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

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

2.分析execve系统调用中断上下文的特殊之处;

3.分析fork子进程启动执行时进程上下文的特殊之处;

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

 

一.中断上下文的切换和进程上下文切换

整个linux内部结构可以分为三部分,从最底层到最上层依次是:硬件-->内核空间-->用户空间。如下网络图片所示:

 

 

 

  程序在执行过程中通常有用户态和内核态两种状态,CPU对处于内核态根据上下文环境进一步细分,因此有了下面三种状态:

(1)内核态,运行于进程上下文,内核代表进程运行于内核空间。

(2)内核态,运行于中断上下文,内核代表硬件运行于内核空间。

(3)用户态,运行于用户空间。

  当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。

  此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。

  当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。

  进程上下文切换:

    用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递 很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的

  一些寄存 器值、变量等。所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。

  相对于进程而言,就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。一个进程的上下文可以

  分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。

  中断上下文的切换:

    硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的 一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓

  的“ 中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。

 

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

  execve系统调用的作用是运行另外一个指定的程序。它会把新程序加载到当前进程的内存空间内,当前的进程会被丢弃,它的堆、栈和所有的段数据都会被新进程相应的部分代替,

然后会从新程序的初始化代码和 main 函数开始运行。同时,进程的 ID 将保持不变。execve系统调用通常与 fork系统调用配合使用。从一个进程中启动另一个程序时,通常是先fork一个

子进程,然后在子进程中使用 execve变为运行指定程序的进程。

  系统调用过程

  sys_execve -> do_execve() –>do_execveat_common() -> __do_execve_file -> exec_binprm()-> search_binary_handler() ->load_binary-> load_elf_binary->start_thread()。

load_elf_binary()里面

/ 为进程分配用户态堆栈,并塞入参数和环境变量
    retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
                 executable_stack);
    current->mm->start_stack = bprm->p;


// 修改保存在内核堆栈,但属于用户态的eip和esp
    start_thread(regs, elf_entry, bprm->p);
    retval = 0;

  初始化ELF进程环境

  将系统调用的返回地址修改为ELF可执行程序的入口点,这个入口点取决于程序的连接方式,对于静态链接的程序其入口就是e_entry,而动态链接的程序其入口是动态链接器

  最后调用start_thread,修改保存在内核堆栈,但属于用户态的eipesp

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

execve系统调用过程:陷入内核,加载新的进程,将新的进程,完全覆盖原先进程的数据空间,将 IP 值设置为新的进程的入口地址,将 IP 值设置为新的进程的入口地址,返回用户态,新程序继续执行下去。老进程的上下文被完全替换,但进程的 pid 不变,所以 execve 系统调用不会返回原进程,而是返回新进程。

 

 

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

  fork系统调用用于从已存在进程中创建一个新进程,新进程称为子进程,而原进程称为父进程。fork调用一次,返回两次,这两个返回分别带回它们各自的返回值,其中在父进程中的返回值是子进程的进程号,而子进程中的返回值则返回 0。因此,可以通过返回值来判定该进程是父进程还是子进程。

   调用过程:sys_fork系统调用-> clone系统调用->do_fork函数

 

 

do_fork()调用了 copy_process 函数,复制当前进程产生子进程,并且传入关键参数为子进程设置响应进程上下文

 

 

 

 

 

 copy_process调用copy_thread,设置子进程的堆栈信息;子进程从ret_from_fork开始执行,所以它的地址赋给thread.ip,也就是将来的eip寄存器

 

 

 

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

 

正在运⾏的⽤户态进程X。

 

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

 

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

 

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

 

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

 

save cs:rip/ss:rsp/rflflags:将当前CPU关键上下⽂压⼊进程X的内核堆栈,快速系统调⽤是由系统调⽤⼊⼝处的汇编代码实现的。• 此时完成了中断上下⽂切换,即从进程X的⽤户态到进程X的内核态。

 

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

 

switch_to调⽤了__switch_to_asm汇编代码做了关键的进程上下⽂切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程(本例假定为进程Y)的内核堆

 

栈,并完成了进程上下⽂所需的指令指针寄存器状态切换。之后开始运⾏进程Y(这⾥进程Y曾经通过以上步骤被切换出去,因此可以从switch_to下⼀⾏代码继续执⾏)。)中断上下⽂恢复,与(3)中断上下⽂切换相对应。注意这⾥是进程Y的中断处理过程中,⽽(3)中断上下⽂切换是在进程X的中断处理过程中,因为内核堆栈从进程X

 

切换到进程Y了。

 

为了对应起⻅中断上下⽂恢复的最后⼀步单独拿出来(6的最后⼀步即是7)iret - pop cs:rip/ss:rsp/rflflags,从Y进程的内核堆栈中弹出(3)中对应的压栈内容。此时完

 

成了中断上下⽂的切换,即从进程Y的内核态返回到进程Y的⽤户态。注意快速系统调⽤返回sysret与iret的处理略有不同。

 

继续运⾏⽤户态进程Y。
 

进程的切换和系统的一般执行过程进程调度的时机:

    (1)中断处理过程(包括时钟中断、I/O中断、系统调用和异常),直接调用schedule(),或者通过need_resched标记调度的schedule()
    (2)用户态进程只能被动调度,即在中断处理过程中进行调度
    (3)内核线程可以直接调用schedule(),也可以在中断处理过程中进行调度,可以主动调度也可以被动调度
     进程上下文切换:挂起正在cpu上运行的进程,与中断时保存现场是不同的,中断前后是在同一个上下文中,只是由用户态转向内核态,但是进程切换是两个进程间的切换

        (1)需要切换的信息:用户地址空间、控制信息、硬件上下文(中断也要保存上下文)等

        (2)schedule()中的context_switch中的switch_to切换寄存器和堆栈的状态、eip的位置

        (3)thread.sp内核堆栈的栈底;thread.ip当前进程的eip

        (4)next进程的第一条指令:next_ip一般是$1f,对于新创建的子进程是ret_from_fork

    switch_to做了关键的进程上下文的切换

    restore_all恢复现场

    0-3G用户态,3gG以上内核态访问

    Linux系统执行过程中的几个特殊情况

  (1)内核线程通过中断处理过程中(用户态进程与内核线程间的切换以及内核进程和内核进程间的切换)没有权限的变化,cs段没有发生变化  

  (2)内核线程主动调度schedule(),只有进程上下文的切换,没有发生中断上下文的切换

  (3)创建子进程的系统调用,子进程返回时的执行起点为ret_form_fork(next_ip=ret_form_fork)

 

posted @ 2020-06-14 15:09  刘自强  阅读(274)  评论(0编辑  收藏  举报