前言
上一篇文章https://www.cnblogs.com/wzzgeorge/p/12952208.html 较系统的介绍了系统调用的基本原理,本文将结合系统调用中断上下文切换和进程上下文切换分析Linux内核的一般执行过程。并以fork和execve系统调用为例分析中断上下文的切换,分析execve系统调用中断上下文的特殊之处,分析fork子进程启动执行时进程上下文的特殊之处。
一、fork的中断上下文切换
fork 和 execve 也是系统调用,但相对于其他系统调用又有些特殊的地方,fork 用于创建一个子进程。
对于普通系统调用,当通过指令int $0x80 或者 syscall 触发系统调用时,系统会在当前进程的内核堆栈上保存?些寄存器的值,包括当前执?程序的用户堆栈栈顶地址(SS:ESP)、当时的状态字(EFlags)、当时的 CS:EIP的值。同时会将当前进程内核堆栈的栈顶地址、内核的状态字等放?CPU 对应的寄存器,并且 CS:EIP 寄存器的值会指向中断处理程序的??,对于系统调?来讲是指向系统调?处理 的??。最后中断处理完毕之后执行 iret 指令,就会把之前保存的关键上下文和现场恢复到 CPU 中。
对于fork系统调用,用户程序调用fork函数实际上会最终调用__do_fork函数,这个函数主要通过调用copy_process()来复制父进程的进程描述符、进程状态设置为TASK_RUNNING、采?写时复制技术逐?复制所有其他进程资源、调?copy_thread_tls初始化?进程内核栈、设置?进程pid等,然后调用wake_up_new_task将子进程加入就绪队列等待调度,最后系统调用返回。copy_thread_tls非常关键,因为这个函数不仅分配了子进程的内核堆栈而且对内核堆栈和thread等进程关键上下文进行了初始化,所以当子进程被调用运行时会直接从fork系统调用的下一条语句开始执行。而对于父进程来说,其关键上下文切换过程像普通系统调用一样如上文所述。
二、execve的中断上下文切换
execve系统调用用于执行一个可执行程序,
三、fork子进程启动执行时的进程上下文切换
首先要理解什么是进程上下文切换:为了控制进程的执?,内核必须有能?挂起正在CPU上运?的进程,并恢复执?以前挂起的某个进程,这种?为被称为进程上下文切换。进程上下文包括?户地址空间(程序代码、数据、?户堆栈等)、控制信息(进程描述符、内核堆栈等)、进程的CPU上下?及相关寄存器的值(这些值保存在进程描述符中)。
进程切换过程可以分为两步,一是切换?全局?录(CR3)以安装?个新的地址空间;二是切换内核态堆栈和进程的CPU上下?,因为进程的CPU上下?提供了内核执?新进程所需要的所有信息,包含所有CPU寄存器状态。需要注意的是进程描述符保存在内核堆栈的低地址处。
前面说到fork系统调用处理函数中会调用copy_thread_tls函数,分配了子进程的内核堆栈而且对内核堆栈和thread等进程关键上下文进行了初始化。那么当该子进程获得CPU开始执行时,执行上述切换操作,然后开始运行。
还有一个很重要的问题是,父进程和子进程fork完成之后的返回值不同,进而可以执行不同的程序分支,这是怎么实现的呢?这是因为__do_fork() 在返回后,会从内核堆栈中的eax读取返回地址,而eax在copy_thread函数中被强制设置为0,因此子进程的返回值就是0。