-------------------------------Linux总结--------------------------------------
目录:
计算机工作的三大法宝:
(1)存储程序计算机工作模型;(2)函数调用堆栈;(3)中断。
目前计算机均采用冯·诺依曼体系结构,存储的程序在内存运行时,会发生函数调用。为了高效地完成该功能,栈机制得以应用。让我们一起来理解一下栈的工作机制。
为了解决能使计算机的工作更为高效,进程调度应运而生。让我们一起实现一个简单的单时间片轮转的微型操作系统,从而理解操作系统是如何对进程进行调度。
当计算机的电源被开启后,计算机的启动过程:加载BIOS->读取MBR->装载Boot(Boot Loader)->加载内核->init初始化->执行rc.sysinit->登录Login。
那么,内核启动过程中,具体过程是怎样的,内核的目录中都是什么内容,让我们一起来研究一下。
四、使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
在Linux中,系统调用是用户空间访问内核的惟一手段;除异常和中断外,它们是内核惟一的合法入口。而kernel留给用户层的接口其实就只有一个:软中断(int 0x80)。用户态通过它来陷入内核态,完成系统调用。
调用一个系统调用有三种方法:(1)通过 glibc 提供的库函数;(2)使用 syscall 函数直接调用;(3)通过 int 0x80指令陷入(前两个本质上是也是由第三个实现的)。而从内核态转变用户态的真正出口是iret。
让我们一起通过两种方式的比较来更好地理解系统调用过程。
总体上讲,该过程为:(1)调用libc库函数中对应的系统调用;(2)中断int 0x80 陷入内核;(3)执行system_call函数,SAVE_ALL保护现场,通过EAX中保存的系统调用号在系统调用表中找到对应的系统调用;(4)执行对应系统调用的代码;(5)最后转入ret_from_sys_call,指令iret从系统调用返回。
让我们一起研究下其具体过程。
进程是程序执行的一个实例,是最小的系统资源分配基本单元。Linux中,进程描述符是由内核定义的一个task_struct类型数据结构。可以通过三种方式来创建一个进程:(1)fork;(2)vfork;(3)clone,而这三种方式,最终都是通过do_fork()实现的。
而一个新创建的进程获得执行权限后,从用户态来看,是从fork语句的下一条语句即为子进程执行的开始;从内核态看,是从ret_from_fork处开始执行的。
让我们一起理解一下do_fork()的具体过程。
Linux系统可以通过execve启动一个新进程。execve负责将新的程序代码和数据替换到新的进程中,打开可执行文件,载入依赖的库文件,申请新内存空间,最后执行start_thread,设置new_ip,new_sp,完成新进程的代码和数据替换,然后返回,接下来就是执行新的进程代码了。
而linux系统中,可执行文件的格式为elf(Executableand Linking Format)格式,让我们一起来看一下这个execve过程与elf格式的具体内容。
Linux进程切换是通过switch_to实现的。switch_to本身是一个宏,通过利用长跳指令,当长跳指令的操作数是TSS描述符的时候,就会引起CPU的任务的切换,此时,cpu将所有寄存器的状态保存到当前任务寄存器TR所指向的TSS段(当前任务的任务状态段)中,然后利用长跳指令的操作数(TSS描述符)找到新任务的TSS段,然后将其中的内容填写到各个寄存器中,最后,将新任务的TSS选择符更新到TR中。这样系统就正式开始运行新切换的任务了。
进程切换的过程就好像一次穿越,其中的内容非常有趣,让我们一起来研究一下。
心得总结:
收获:
虽然以前也经常使用Linux系统,但是这是我第一次学习真正关于内核Kernel的知识。对于内核的学习,每个人都有自己的学习方法。而对我言,我觉得最大的体会就是,刚开始学内核的时候,不要执着于一个方面,不要专注于一个子系统就一头扎到实际的代码行中去。整个Kernel内核的内容真的是实在太负责,牵涉的面非常广,容易走进死胡同。而应该先从总体思考,这个部分从逻辑上应该做些什么,用什么样的实现方法,避免“只见树木,不见森林”的情况,然后再从具体代码中寻找证实。
而且这次课程让我从内核的角度来思考了具体一个进程是如何转换成另一个进程的,之前对这里的理解一直是我的薄弱之处。
正在运行的用户态进程X切换到运行用户态进程Y的过程要经过以下步骤
(1)正在运行的用户态进程X;
(2)发生中断:savecs:eip/esp/eflags(current) to kernel stack, then load cs:eip(entry of aspecific ISR) and ss:esp(point to kernel stack);
(3)SAVE_ALL //保存现场,这里是已经进入内核中断处里过程;
(4)中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换;
(5)标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行;
(6)restore_all//恢复现场;
(7)iret - popcs:eip/ss:esp/eflags from kernel stack;
(8)继续运行用户态进程Y。
遗憾:
整个Kernel内核实在是太庞大了,每次做实验的时候,都会有好多问题,但是由于时间限制,每次和实验相关度不大的问题就没有进行深入研究。不管怎么说,这次也算是对Kernel的理解开始入门,接下来继续一点点研究吧。
而且操作系统是介于底层硬件和应用软件之间的接口,其各个子系统的实现很大程度上依赖于硬件特性。而对硬件,我的了解就十分有限了。
------------------------------------END----------------------------------------
刘建鑫 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程
http://mooc.study.163.com/course/USTC-1000029000
----------------------------------------------------------------------------------