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

一、实验环境

os: linux   

虚拟机:QEMU   

内核版本 5.3.4   

调试方法:GDB

fork系统的调用过程

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

fork函数的不同在于,os依照父进程的堆栈空间,复制了一份一模一样的堆栈空间给子进程,不过改变了子进程的进程号,所以子进程中也有一个fork函数,子进程从父进程fork后开始执行,子进程的fork函数会返回0  父进程的fork函数会返回子进程的进程号代表子进程创建成功

do fork系统调用的过程

_do_fork

  • copy_process             复制进程描述符和执?时所需的其他数据结构
    • dup_task_struct          复制进程描述符task_struct、创建内核堆栈等
    • copy_thread_tls          初始化?进程内核栈和thread
  • wake_up_new_task    将?进程添加到就绪队列

系统调用返回

fork系统调用实验

编写程序,使用fork函数

#include "func.h"

int g=10;

int main()
{
        pid_t pid;
        pid = fork();
        int fd = open("file",O_RDWR);
        if(0==pid)
        {
                printf("I AM CHILD,mypid=%d,mydad_pid=%d\n",getpid(),getppid());

        }else
        {
                printf("I AM FATHER,mypid=%d,mychild_pid = %d\n",getpid(),pid);
                sleep(2);
                return 0;
        }
}

编译后执行结果

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

开启虚拟机,在__x64_sys_clone ,_do_forkcpoy_processdup_task_structcopy_thread_tls下断点,shell下运行fork可执行文件,查看此时函数栈

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

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

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

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

execv系统调用

当前的可执?程序在执?,执?到execve系统调?时陷?内核态,在内核???do_execve加载可执??件,把当前进程的可执?程序给覆盖掉。当execve系统调?返回 时,返回的已经不是原来的那个可执?程序了,?是新的可执?程序。

exec函数都是通过execve系统调?进?内核,对应的系统调?内核处理函数为sys_execve__x64_sys_execve,它们都是通过调?do_execve来具体执?加载可执??件的 ?作。

整体的调?的递进关系为:

  • sys_execve()或__x64_sys_execve -> // 内核处理函数
  • do_execve() –> // 系统调用函数
  • do_execveat_common() -> // 系统调用函数
  • __do_execve_?le ->
  • exec_binprm()-> // 根据读入文件头部,寻找该文件的处理函数
  • search_binary_handler() ->
  • load_elf_binary() -> // 加载elf文件到内存中
  • start_thread() // 开始新进程

三、进程切换

进程切换时机:

1.进程时间片用完

2.进程执行过程中遇到了 中断

3.内核线程主动调?schedule函数进?进程调度

进程上下文

  • ?户地址空间:包括程序代码、数据、?户堆栈等。 (CR3寄存器代表进程??录表,即地址空间、数据)
  • 控制信息:进程描述符(thread)、内核堆栈(sp寄存器)等。
  • 进程的CPU上下?,相关寄存器的值(指令指针寄存器ip代表进程的CPU上下?)

进程切换的过程

  • 切换?全局?录(CR3)以安装?个新的地址空间,这样不同进程的虚拟地 址如0x8048400(32位x86)就会经过不同的?表转换为不同的物理地址。
  • 切换内核态堆栈和进程的CPU上下?,因为进程的CPU上下?提供了内核执 ?新进程所需要的所有信息,包含所有CPU寄存器状态。

核心代码

((last) = __switch_to_asm((prev), (next)));
      ENTRY(__switch_to_asm)     
      pushq    %rbp     
      pushq    %rbx     
      pushq    %r12     
      pushq    %r13     
      pushq    %r14     
      pushq    %r15     
      /* switch stack */     
      movq    %rsp, TASK_threadsp(%rdi)     
      movq    TASK_threadsp(%rsi), %rsp   
      popq    %r15     
      popq    %r14     
      popq    %r13     
      popq    %r12     
      popq    %rbx     
      popq    %rbp     
      jmp    __switch_to END(__switch_to)

__switch_to_asm是在C代码中调?的,也就是使?call指令,?这段汇编的结尾是jmp __switch_to, __switch_to函数是C代码最后有个return,也就是ret指令。将__switch_to_asm和__switch_to结合起来,正好是call指令和ret指令的配对出现。

call指令压栈RIP寄存器到进程切换前的prev进程内核堆栈;?ret指令出栈存?RIP 寄存器的是进程切换之后的next进程的内核堆栈栈顶数据。

由此完成了进程的切换。

中断上下文和进程上下文对比

中断上下文的切换

中断是由CPU实现的,所以中断上下?切换过程中最关键的栈顶寄存器sp和指令指针寄存器 ip 是由CPU协助完成的。

进程上下文的切换

进程切换是由内核实现的(且一般情况下,进程上下文切换嵌套在中断中),所以进程上下?切换过程最关键的栈顶寄存器sp切换是通过进程描述符的thread.sp实现的,指令指针 寄存器ip的切换是在内核堆栈切换的基础上巧妙利?call/ret指令实现的。

相关推荐