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

  • 以fork和execve系统调用为例分析中断上下文的切换
  • 分析execve系统调用中断上下文的特殊之处
  • 分析fork子进程启动执行时进程上下文的特殊之处
  • 以系统调用作为特殊的中断,结合中断上下文切换和进程上下文切换分析Linux系统的一般执行过程

一、以fork和execve系统调用为例分析中断上下文的切换

fork系统调用:fork由父进程创建一子进程,新创建的子进程几乎但是不完全与父进程相同。

  子进程得到与父进程用户级虚拟地址空间相同(但是独立)的一份拷贝,包括文本,数据和bss段、堆以及用户栈。

  子进程还获得与父进程任何打开文件描述符相同的拷贝。这就是意味着当父进程调用fork时候,子进程还可以读写父进程中打开的任何文件。

父进程和新创建的子进程之间最大区别在于他们有着不同的PID。

 

fork系统调用通过do_fork函数来创建新进程,_do_fork函数主要完成了调⽤copy_process()复制⽗进程、获得pid、调wake_up_new_task将⼦进程加⼊就绪队列等待调度执⾏等。

 

 

   copy_process()是创建进程过程中的主要部分。其主要完成了:

    调⽤dup_task_struct复制当前进程(⽗进程)描述task_struct

    信息检查、

    初始化、

    把进程状态设置为TASK_RUNNING(此时⼦进程置为就绪态)、

    采⽤写时复制技术逐⼀复制所有其他进程资源、

    调⽤copy_thread_tls初始化⼦进程内核栈、

    设置⼦进程pid等。

 

  其中最关键的就是dup_task_struct,其复制当前进程(⽗进程)描述task_structcopy_thread_tls用于初始化⼦进程内核栈。

 

 

 

 

   copy_thread_tls负责构造fork系统调用在子进程的内核堆栈,子进程的内核函数调用堆栈需要特殊构建,为子进程的运行准备好上下文环境。

 

 

 

 

execve系统调用的主要作用:

  分配进程新的地址空间,将环境变量、main参数等拷贝到新地址空间的推栈中;
  解析可执行文件,将代码、数据装入/映射到内存
  进程新环境的设置,如关闭设置FD_CLOEXEC的文件等
  设置execve函数返回到用户态时的执行地址;解析器入口地址或程序的入口地址

 

   execve系统调用主要通过do_execve函数来实现

do_execve函数具体流程如下:
  进程分配并使用自己的文件描述符表,不再使用共享的文件描述符表
  分配进程新的地址空间mm_struct,并复制老mm_struct的context,并分配一页大小的堆栈内存区。
  打开可执行文件;检查可执行文件权限,并预读可执行文件的前BINPRM_BUF_SIZE=128字节数据到buf中
  计算环境变量、main参数个数;并将这些参数复制到新地址空间的堆栈中
  根据可执行文件的格式,查找对应的handler作后续处理;

 

 

二、分析execve系统调用中断上下文的特殊之处

execve系统调用在保持进程ID不变的情况下,使用新的上下文环境“覆盖”原上下文环境,也即原来的上下文环境不再保存,相关的环境数据直接被丢弃。 

 

三、分析fork子进程启动执行时进程上下文的特殊之处

 fork子进程的进程上下文需要借由copy_thread_tls函数特殊构建:子进程的复制了父进程所有的上下文环境信息,但二者的PID不同,各自作为独立的进程被调度。

 

 

四、以系统调用作为特殊的中断,结合中断上下文切换和进程上下文切换分析Linux系统的一般执行过程

32x86系统结构linux-3.18.6为例,以系统调⽤作为特殊的中断简要总结如下。

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

2)发⽣中断(包括异常、系统调⽤等),CPU完成以下动作。

  • save cs:eip/ss:esp/eflags:当前CPU上下⽂压⼊进程X的内核堆栈。

  • load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack):加载当前进程内核堆栈相关信息,跳转到中断处理程序,即中断执⾏路径的起点。

3SAVE_ALL,保存现场,此时完成了中断上下⽂切换,即从进程X的⽤户态到进程X的内核态。

4)中断处理过程中或中断返回前调⽤了schedule函数,其中的switch_to做了关键的进程上下⽂切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程(本例假定为进程Y)的内核堆栈,并完成了进程上下⽂所需的EIP等寄存器状态切换。

5)标号1,即前述3.18.6内核的swtich_to代码第50⾏“”1:\t“ ”(地址为switch_to中的“$1f”),之后开始运⾏进程Y(这⾥进程Y曾经通过以上步骤被切换出去,因此可以从标号1继续执⾏)。

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

7iret - pop cs:eip/ss:esp/eflags,从Y进程的内核堆栈中弹出(2)中硬件完成的压栈内容。此时完成了中断上下⽂的切换,即从进程Y的内核态返回到进程Y的⽤户态。

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

 

 

 

posted @ 2020-06-15 17:49  HermitTravis  阅读(151)  评论(0编辑  收藏  举报