1.以fork和execve系统调用为例分析中断上下文的切换
(1)fork系统调用
使用fork库函数,c代码如下
反汇编之后,可以看到汇编代码如下
可以看到系统调用号保存在ax中,fork系统调用号为0x38,之后执行syscall指令,进入系统调用入口,entry_SYSCALL_64,用gdb追踪执行过程
在entry_SYSCALL_64中,保存现场,用swapgs保存快照,调用do_syscall_64
在do_syscall_64中利用系统调用号在syscall_64.tbl找到对应的系统调用函数,转到具体的内核中的系统调用函数执行,可以看出在这里使用了__x64_sys_clone函数
在__x64_sys_clone函数中,返回_do_fork()函数,在_do_fork函数中完成子进程的创建
在_do_fork函数中,使用copy_process函数,进行复制进程描述符和执⾏时所需的其他数据结构的操作
在copy_process函数中,使用dup_task_struct进行复制进程描述符task_struct、创建内核堆栈等操作
在dup_task_struct中,使用arch_dup_task_struct完成进程描述符的拷贝,让tsk指针指向org指针。
在copy_process函数中,使用copy_thread_tls构造fork系统调⽤在⼦进程的内核堆栈
(2)execve系统调用
c代码如下, 先fork一个子进程,在子进程中使用execlp,执行可执行程序ls。
用gdb追踪execve系统调用过程:
系统调用入口为__x64_sys_execve
进入其中的do_execveat_common函数
进入其中的__do_execve_fifile函数
进入其中的exec_binprm()函数
进入其中的search_binary_handler函数,找到解析当前文件的程序入口
进入其中的load_elf_binary函数,进行校检文件,加载文件到内存并映射到进程的地址空间
进入其中的start_thread函数,配置进程启动上下文环境
2.分析execve系统调用中断上下文的特殊之处
execve执行后,加载新的可执行程序,会把当前进程的可执行程序覆盖,execve系统调用返回时将不会返回到调用execve的地方,而是新的可执行程序main函数的入口。
3.分析fork子进程启动执行时进程上下文的特殊之处
由于fork创建了子进程,而子进程需要进行系统调用返回时,内核函数调用堆栈中没有用于返回的地址和一些必要框架,所以在_do_fork函数中的copy_process函数中,使用copy_thread_tls构造fork系统调⽤在⼦进程的内核堆栈,使堆栈包含两部分,struct pt_regs regs和struct inactive_task_frame frame,struct pt_regs regs为一般系统调用时所需的堆栈栈底,struct inactive_task_frame frame包含寄存器信息和返回的地址ret_addr,通过该结构,可以使子进程正常的系统调用返回。
4.以系统调用作为特殊的中断,结合中断上下文切换和进程上下文切换分析Linux系统的一般执行过程
以64位系统为例,首先一个进程正在执行,此时发生系统调用或异常,产生中断,由cpu跳转到中断处理程序调用入口,在入口内,使用swapgs保存现场,之后加载当前进程内核堆栈栈顶地址到RSP寄存器,将当前CPU关键上下⽂压⼊内核堆栈,使进程由用户态切换到内核态,中断处理完成后的调度时机或中断处理过程中进行调度,调用schedule函数,通过进程调度算法选择要被调度的进程,其中的switch_to函数进行进程上下文切换,保存寄存器,将内核堆栈切换为被选中的next进程的内核堆栈,将寄存器切换为next进程,由于next之前使用此方法调度至其他进程,所以直接可以接着执行next进程,next进程中断处理完成后回到next进程用户态,继续运行next进程。