一、实验要求
结合中断上下文切换和进程上下文切换分析Linux内核一般执行过程
-
以fork和execve系统调用为例分析中断上下文的切换
-
分析execve系统调用中断上下文的特殊之处
-
分析fork子进程启动执行时进程上下文的特殊之处
-
以系统调用作为特殊的中断,结合中断上下文切换和进程上下文切换分析Linux系统的一般执行过程
二、execve系统调用
Linux系统一般会提供了execl、execlp、execle、execv、execvp和execve等6个用以加载执行一个可执行文件的库函数,这些库函数统称为exec函数,差异在于对命令行参数和环境变量参数的传递方式不同。exec函数都是通过execve系统调用进入内核,对应的系统调用内核处理函数为sys_execve或__x64_sys_execve,它们都是通过调用do_execve来具体执行加载可执行文件的工作。
exec函数族就实现了在一个进程中启动另外一个程序的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。
整体的调用关系为sys_execve()或__x64_sys_execve -> do_execve() –>do_execveat_common() -> __do_execve_file -> exec_binprm()-> search_binary_handler() ->oad_elf_binary() -> start_thread()。
exec_binprm()函数具体如下:
static int exec_binprm(struct linux_binprm *bprm) { pid_t old_pid, old_vpid; int ret; old_pid = current->pid; rcu_read_lock(); old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent)); rcu_read_unlock(); <strong>ret = search_binary_handler(bprm);</strong> if (ret >= 0) { audit_bprm(bprm); trace_sched_process_exec(current, old_pid, bprm); ptrace_event(PTRACE_EVENT_EXEC, old_vpid); proc_exec_connector(current); } return ret; }
三、fork系统调用过程
fork系统调用在陷?内核态之后有两次返回:
- 第?次返回到原来的?进程的位置继续向下执?
- 在?进程中fork也返回了?次,会返回到ret_from_fork,再经过系统调?返回到?户态
在fork系统调用中,最重要的的就是_do_fork函数:do_fork函数主要完成了调?copy_process()复制?进程、获得pid、调?wake_up_new_task将?进程加?就绪队列等待调度执?等。
long _do_fork(struct kernel_clone_args *args) { u64 clone_flags = args->flags; struct completion vfork; struct pid *pid; struct task_struct *p; int trace = 0; long nr; /* * Determine whether and which event to report to ptracer. When * called from kernel_thread or CLONE_UNTRACED is explicitly * requested, no event is reported; otherwise, report if the event * for the type of forking is enabled. */ if (!(clone_flags & CLONE_UNTRACED)) { if (clone_flags & CLONE_VFORK) trace = PTRACE_EVENT_VFORK; else if (args->exit_signal != SIGCHLD) trace = PTRACE_EVENT_CLONE; else trace = PTRACE_EVENT_FORK; if (likely(!ptrace_event_enabled(current, trace))) trace = 0; } // 拷贝父进程task_struct以及其中的一些资源,返回创建的task_struct的指针 p = copy_process(NULL, trace, NUMA_NO_NODE, args); add_latent_entropy(); if (IS_ERR(p)) return PTR_ERR(p); /* * Do this prior waking up the new thread - the thread pointer * might get invalid after that point, if the thread exits quickly. */ trace_sched_process_fork(current, p); // 取出task结构体内的pid pid = get_task_pid(p, PIDTYPE_PID); nr = pid_vnr(pid); if (clone_flags & CLONE_PARENT_SETTID) put_user(nr, args->parent_tid); // 如果使用的是vfork,那么必须采用某种完成机制,确保父进程后运行 if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); get_task_struct(p); } // 将子进程添加到调度器的队列,使得子进程有机会获得CPU wake_up_new_task(p); /* forking complete and child started to run, tell ptracer */ if (unlikely(trace)) ptrace_event_pid(trace, pid); // 如果设置了 CLONE_VFORK 则将父进程插入等待队列,并挂起父进程直到子进程释放自己的内存空间,保证子进程优先于父进程运行 if (clone_flags & CLONE_VFORK) { if (!wait_for_vfork_done(p, &vfork)) ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid); } put_pid(pid); return nr; }
其中copy_process() 主要做了如下工作:
a. 调用 dup_task_struct 复制一份task_struct结构体,作为子进程的进程描述符;
b. 初始化与调度有关的数据结构,调用了sched_fork,这里将子进程的state设置为TASK_RUNNING;
c. 复制所有的进程信息,包括fs、信号处理函数、信号、内存空间(包括写时复制)等;
d.调用copy_thread,设置子进程的堆栈信息;
e. 为子进程分配一个pid。
四、Linux系统的一般执行过程
- 正在运?的?户态进程X。
- 发?中断(包括异常、系统调?等),CPU完成load cs:rip(entry of a speci?c ISR),即跳转到中断处理程序??。
- 中断上下?切换,具体包括如下?点:
- swapgs指令保存现场,可以理解CPU通过swapgs指令给当前CPU寄存器状态做了?个快照。
- rsp point to kernel stack,加载当前进程内核堆栈栈顶地址到RSP寄存器。快速系统调?是由系统调???处的汇编代码实现?户堆栈和内核堆栈的切换。
- save cs:rip/ss:rsp/r?ags:将当前CPU关键上下?压?进程X的内核堆栈,快速系统调?是由系统调???处的汇编代码实现的。
此时完成了中断上下?切换,即从进程X的?户态到进程X的内核态。
- 中断处理过程中或中断返回前调?了schedule函数,其中完成了进程调度算法选择next进程、进程地址空间切换、以及switch_to关键的进程上下?切换等。
- switch_to调?了__switch_to_asm汇编代码做了关键的进程上下?切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程(本例假定为进程Y)的内核堆栈,并完成了进程上下?所需的指令指针寄存器状态切换。之后开始运?进程Y(这?进程Y曾经通过以上步骤被切换出去,因此可以从switch_to下??代码继续执?)。
- 中断上下?恢复,与(3)中断上下?切换相对应。注意这?是进程Y的中断处理过程中,?(3)中断上下?切换是在进程X的中断处理过程中,因为内核堆栈从进程X 切换到进程Y了。
- 为了对应起?,中断上下?恢复的最后?步单独拿出来(6的最后?步即是7)iret - pop cs:rip/ss:rsp/r?ags,从Y进程的内核堆栈中弹出(3)中对应的压栈内容。此时完 成了中断上下?的切换,即从进程Y的内核态返回到进程Y的?户态。注意快速系统调?返回sysret与iret的处理略有不同。
- 继续运??户态进程Y。