结合中断上下文切换和进程上下文切换分析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_fork
,copy_process
,dup_task_struct
,copy_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_?le -> 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。