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

一、实验要求

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

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

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

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

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

二、fork系统调用过程

2.1 理论分析

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

fork系统调用在陷?内核态之后有两次返回:

  1. 第?次返回到原来的?进程的位置继续向下执?
  2. 在?进程中fork也返回了?次,会返回到ret_from_fork,再经过系统调?返回到?户态

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

fork系统调用中,最重要的的就是_do_fork函数:

_do_fork函数主要完成了调?copy_process()复制?进程、获得pid、调
?wake_up_new_task将?进程加?就绪队列等待调度执?等

copy_process()函数:主要是dup_task_structcopy_thread_tls

  1. dup_task_struct:主要是复制当前进程(?进程)描述符task_struct和copy_thread_tls初始化子进程内核栈
  2. copy_thread_tls:负责构造fork系统调用在子进程的内核堆,也就是fok系统调用在父子进程各返回一次,父进程中和其他系统调用的处理过程并无二致,而在子进程中的内核函数调用堆栈需要特殊构建,为子进程的运行准备好上下文环境

2.2 fork系统调用测试

编写fork调用代码并测试:

#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)
    {
       fprintf(stderr,"调用失败");
       exit(-1);
    }
    else if(pid==0)
    {
       printf("子进程 \n");
    }
    else
    {
       printf("父进程 \n");
       wait(NULL);
       printf("子进程完成 \n");
    }
    return 0;
}

运行结果如下:

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

使用反汇编指令得到:该程序使用56

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

/linux-5.4.34/arch/x86/entry/syscalls/syscall_64.tbl中查询56号可以看见:__x64_sys_clone/ptregs

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

与实验二中一样:

重新制作根文件系统,并启动qemu

find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz

cd ..

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

重新打开一个终端,并运行以下命令:

# 打开gdb
gdb vmlinux
target remote:1234

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

在qemu窗口查看:

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

# 并在gdb中对__x64_sys_clone进行断点
b __x64_sys_clone

最终结果如下:

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

可以看出:如理论分析中一样

三、execve系统调用过程

3.1 理论分析

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

exec函数族就实现了在一个进程中启动另外一个程序的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。

执行过程大概如下:

call *sys_execve
……..do_execve
…………….. do_execve_common
……………………. exec_binprm
……………………………..search_binary_handler(bprm)
…………………………………..linux_binfmt= elf_format
……………………………………….elf_format-> load_elf_binary
……………………………………….load_elf_binary
……………………………………………..start_thread
ret

3.2 具体实现

调用do_execve

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

do_execve:

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

do_execve_common:

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

exec_binprm:

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

后续一一对应。

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

  1. 正在运?的?户态进程X。
  2. 发?中断(包括异常、系统调?等),CPU完成load cs:rip(entry of a speci?c ISR),即跳转到中断处理程序??。
  3. 中断上下?切换,具体包括如下?点:
  • swapgs指令保存现场,可以理解CPU通过swapgs指令给当前CPU寄存器状态做了?个快照。
  • rsp point to kernel stack,加载当前进程内核堆栈栈顶地址到RSP寄存器。快速系统调?是由系统调???处的汇编代码实现?户堆栈和内核堆栈的切换。
  • save cs:rip/ss:rsp/r?ags:将当前CPU关键上下?压?进程X的内核堆栈,快速系统调?是由系统调???处的汇编代码实现的。

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

  1. 中断处理过程中或中断返回前调?了schedule函数,其中完成了进程调度算法选择next进程、进程地址空间切换、以及switch_to关键的进程上下?切换等。
  2. switch_to调?了__switch_to_asm汇编代码做了关键的进程上下?切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程(本例假定为进程Y)的内核堆栈,并完成了进程上下?所需的指令指针寄存器状态切换。之后开始运?进程Y(这?进程Y曾经通过以上步骤被切换出去,因此可以从switch_to下??代码继续执?)。
  3. 中断上下?恢复,与(3)中断上下?切换相对应。注意这?是进程Y的中断处理过程中,?(3)中断上下?切换是在进程X的中断处理过程中,因为内核堆栈从进程X 切换到进程Y了。
  4. 为了对应起?,中断上下?恢复的最后?步单独拿出来(6的最后?步即是7)iret - pop cs:rip/ss:rsp/r?ags,从Y进程的内核堆栈中弹出(3)中对应的压栈内容。此时完 成了中断上下?的切换,即从进程Y的内核态返回到进程Y的?户态。注意快速系统调?返回sysret与iret的处理略有不同。
  5. 继续运??户态进程Y。

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

上一篇:open file cache提升nginx性能


下一篇:linux系统free查看内存,发现可用物理内存很少,但是查看进程却发现没进程占用大内存