结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程

一、fork系统调用

  fork系统调用用于创建一个新进程,称为子进程,它与进程(称为系统调用fork的进程)同时运行,此进程称为父进程。创建新的子进程后,两个进程将执行fork()系统调用之后的下一条指令。子进程使用相同的pc(程序计数器),相同的CPU寄存器,在父进程中使用的相同打开文件。
 
  它不需要参数并返回一个整数值。下面是fork()返回的不同值。
    负值:创建子进程失败。
    零:返回到新创建的子进程。
    正值:返回父进程或调电者。该值包含新创建的子进程的进程ID
 
  fork是⼀个系统调⽤,和⼀般的系统调⽤执⾏过程⼤致是⼀样的。

  首先用户程序调用fork()发出一个软中断,由entry_SYSCALL_64 ()进入系统调用入口,它会保存用户态现场,调出内核态现场,包括在内核堆栈上保存⼀些寄存器的值,包括用户堆栈栈顶地址(SS:ESP)、当时的状态字(EFlags)、当时的 CS:EIP的值。同时会将当前进程内核堆栈的栈顶地址、内核的状态字等放⼊CPU 对应的寄存器,并且 CS:EIP 寄存器的值会指向中断处理程序的⼊⼝,

  再由do_syscall_64根据系统调用号来执行相应的系统调用函数,进而调用_do_fork()函数完成相应功能。_do_fork函数主要完成了调⽤copy_process()创建子进程,再调⽤wake_up_new_task将⼦进程加⼊就绪队列等待调度执⾏等。

  之后恢复用户态现场,返回用户态,继续向下执行。

  ⼦进程复制了⽗进程中所有的进程上下文信息,包括内核堆栈、进程描述符等,⼦进程作为⼀个独⽴的进程也会被调度。复制⽗进程的资源时采⽤了Copy OnWrite(写时复制)技术,不需要修改的进程资源⽗⼦进程是共享内存存储空间的。

二、execve系统调用

  execve系统调用的作用是运行另外一个指定的程序。它会把新程序加载到当前进程的内存空间内,当前的进程会被丢弃,它的堆、栈和所有的段数据都会被新进程相应的部分代替,然后会从新程序的初始化代码和 main 函数开始运行。同时,进程的 ID 将保持不变。

  execve系统调用通常与 fork系统调用配合使用。从一个进程中启动另一个程序时,通常是先 fork一个子进程,然后在子进程中使用 execve变身为运行指定程序的进程。 例如,当用户在 Shell 下输入一条命令启动指定程序时,Shell 就是先 fork了自身进程,然后在子进程中使用 execve来运行指定的程序。

  execve系统调用的过程大概如下:

      1.陷入内核;

      2.加载新的可执行文件并进行可执行性检查;

      3.将新的可执行文件映射到当前运行进程的进程空间中,并覆盖原来的进程数据;

      4.将EIP的值设置为新的可执行程序的入口地址。

      5.返回用户态,程序从新的EIP出开始继续往下执行。至此,老进程的上下文已经被新的进程完全替代了,但是进程的PID还是原来的。

  execve系统调用中断上下文的特殊之处在于,新的运行进程中已经找不到原来的对execve调用的代码了,所以execve函数不会成功返回,而是实现了一次完全的变身。

三、Linux系统的一般执行过程

• (1)正在运⾏的⽤户态进程X。

• (2)发⽣中断(包括异常、系统调⽤等),CPU完成load cs:rip(entry of a specific ISR),即跳转到中断处理程序⼊⼝。

• (3)中断上下⽂切换,具体包括如下⼏点:

• • swapgs指令保存现场,可以理解CPU通过swapgs指令给当前CPU寄存器状态做了⼀个快照。

• • rsp point to kernel stack,加载当前进程内核堆栈栈顶地址到RSP寄存器。快速系统调⽤是由系统调⽤⼊⼝处的汇编代码实现⽤户堆栈和内核堆栈的切换。

• • save cs:rip/ss:rsp/rflags:将当前CPU关键上下⽂压⼊进程X的内核堆栈,快速系统调⽤是由系统调⽤⼊⼝处的汇编代码实现的。

• 此时完成了中断上下⽂切换,即从进程X的⽤户态到进程X的内核态。

• (4)中断处理过程中或中断返回前调⽤了schedule函数,其中完成了进程调度算法选择next进程、进程地址空间切换、以及switch_to关键的进程上下⽂切换等。

• (5)switch_to调⽤了__switch_to_asm汇编代码做了关键的进程上下⽂切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程(本例假定为进程Y)的内核堆 栈,并完成了进程上下⽂所需的指令指针寄存器状态切换。之后开始运⾏进程Y(这⾥进程Y曾经通过以上步骤被切换出去,因此可以从switch_to下⼀⾏代码继续执⾏)。

• (6)中断上下⽂恢复,与(3)中断上下⽂切换相对应。注意这⾥是进程Y的中断处理过程中,⽽(3)中断上下⽂切换是在进程X的中断处理过程中,因为内核堆栈从进程X 切换到进程Y了。

• (7)为了对应起⻅中断上下⽂恢复的最后⼀步单独拿出来(6的最后⼀步即是7)iret - pop cs:rip/ss:rsp/rflags,从Y进程的内核堆栈中弹出(3)中对应的压栈内容。此时完 成了中断上下⽂的切换,即从进程Y的内核态返回到进程Y的⽤户态。注意快速系统调⽤返回sysret与iret的处理略有不同。

• (8)继续运⾏⽤户态进程Y

posted @ 2020-06-15 16:03  广宇  阅读(185)  评论(0)    收藏  举报