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

Posted ustcsselee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程相关的知识,希望对你有一定的参考价值。

 

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

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

一、fork和execve系统调用的区别与联系

•fork两次返回,第一次返回到父进程继续向下执行,第二次子进程返回到ret_from_fork后正常返回到用户态,其通过do_fork函数创建进程,其大概过程如下:

long _do_fork(struct kernel_clone_args *args) {    
    .....
    //复制进程描述符和执⾏时所需的其他数据结构       
    p = copy_process(NULL, trace, NUMA_NO_NODE, args);    
    ......
    wake_up_new_task(p);//将⼦进程添加到就绪队列    
    .......
    return nr;//返回⼦进程pid(⽗进程中fork返回值为⼦进程的pid)
 }

 

_do_fork函数主要完成了调⽤copy_process()复制⽗进程、获得pid、调⽤wake_up_new_task将⼦进程加⼊就绪队列等待调度执⾏等、通过clone_flags标志进行一些辅助性工作等。

 

•execve在执行时陷入内核态,用execve中加载的程序把当前正在执行的进程覆盖掉,当系统调用返回时返回到新的可执行程序起点,程序执⾏的起点,静态链接的可执⾏⽂件也就是main函数的⼤致位置,动态链接的可执⾏⽂件还需
要ld链接好动态链接库再从main函数开始执⾏。其函数调用的实质是运行内核态的sys_execve()函数,大致过程如下:

(1).sys_execve中的do_execve()读取128个字节的文件头部,以此判定可执行文件的类型;

  (2).调用search_binary_handler()去搜索和匹配合适的可执行文件的装载处理过程;

(3).EIF文件由load_elf_binary()函数负责装载。

 

二、fork系统调用

(1).创建子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char* argv[])
{
    int pid;

    pid = fork();
    if(pid<0)
    {
      //error
       fprintf(stderr,"For Failed");
       exit(-1);
    }
    else if(pid==0)
    {
       //child
       printf("this is child process \\n");
    }
    else
    {
       //parent
       printf("this is Parent process \\n");
       wait(NULL);
       printf("child complete \\n");
    }
    return 0;
}

(2).编译、执行:

gcc -o fork fork.c -static -m64
./fork

 

 

(3).反汇编 

objdump -S fork > fork.s

 

  打开fork.s文件可查询如下:

 

 查询448f60有:

 

 对应56号,查询/linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl,有:

 

 即内核函数调用__64_sys_clone,查询linux-5.4.34/kernel/fork.c,有:

 

可确定sys_clone函数返回值就是do_fork(),接下来在__x64_sys_clone_do_forkcopy_processdup_task_structcopy_thread_tls下断点,shell下运行fork可执行文件,查看此时函数栈。

qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"
gdb vmlinux

 

 

 

断点设置完成后,shell下运行./fork,有:

 

 

 

 按照该顺序得到最终结果。

三、execve系统调用

 

execve和其他系统调⽤不同之处是加载完新的可执⾏程序之后已经覆盖了原来⽗进程的上下⽂环境。通过调用栈可以看出execve的调用关系为:

__x64_sys_execve -> do_execve() –> do_execveat_common() -> __do_execve_file -> exec_binprm()-> search_binary_handler() -> load_elf_binary() -> start_thread()

 

 

 

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

以32位x86系统结构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):加载当前进程内核堆栈相关信息,跳转到中断处理程序,即中断执⾏路径的起点。
(3)SAVE_ALL,保存现场,此时完成了中断上下⽂切换,即从进程X的⽤户态到进程X的内核态。
(4)中断处理过程中或中断返回前调⽤了schedule函数,其中的switch_to做了关键的进程上下⽂切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程
(5)标号1,即前述3.18.6内核的swtich_to代码第50⾏“”1:\\t“ ”(地址为switch_to中的“$1f”),之后开始运⾏进程Y(这⾥进程Y曾经通过以上步骤被切换出去,因此可以
从标号1继续执⾏)。

(6)restore_all,恢复现场,与(3)中保存现场相对应。注意这⾥是进程Y的中断处理过程中,⽽(3)中保存现场是在进程X的中断处理过程中,因为内核堆栈从进程X
切换到进程Y了。
(7)iret - pop cs:eip/ss:esp/eflags,从Y进程的内核堆栈中弹出(2)中硬件完成的压栈内容。此时完成了中断上下⽂的切换,即从进程Y的内核态返回到进程Y的⽤户
态。
(8)继续运⾏⽤户态进程Y。

以上是关于结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

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

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

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