结合中断上下文切换和进程上下文切换分析Linux内核一般执行过程
- 以fork和execve系统调用为例分析中断上下文的切换
- 分析execve系统调用中断上下文的特殊之处
- 分析fork子进程启动执行时进程上下文的特殊之处
- 以系统调用作为特殊的中断,结合中断上下文切换和进程上下文切换分析Linux系统的一般执行过程
一、fork系统调用
零:返回到新创建的子进程。
正值:返回父进程或调电者。该值包含新创建的子进程的进程ID。
首先用户程序调用fork()发出一个软中断,由entry_SYSCALL_64 ()进入系统调用入口,保存用户态现场,进入内核态,再由do_syscall_64根据系统调用号来执行相应的系统调用函数,进而调用_do_fork()函数完成相应功能。
_do_fork函数主要完成了调?copy_process()创建子进程,再调?wake_up_new_task将?进程加?就绪队列等待调度执?等。
之后恢复用户态现场,返回用户态,继续向下执行。
?进程复制了?进程中所有的进程上下文信息,包括内核堆栈、进程描述符等,?进程作为?个独?的进程也会被调度。复制?进程的资源时采?了Copy OnWrite(写时复制)技术,不需要修改的进程资源??进程是共享内存存储空间的。
二、execve系统调用
execve系统调用的作用是运行另外一个指定的程序。它会把新程序加载到当前进程的内存空间内,当前的进程会被丢弃,它的堆、栈和所有的段数据都会被新进程相应的部分代替,然后会从新程序的初始化代码和 main 函数开始运行。同时,进程的 ID 将保持不变。
execve系统调用通常与 fork系统调用配合使用。从一个进程中启动另一个程序时,通常是先 fork一个子进程,然后在子进程中使用 execve变身为运行指定程序的进程。 例如,当用户在 Shell 下输入一条命令启动指定程序时,Shell 就是先 fork了自身进程,然后在子进程中使用 execve来运行指定的程序。
execve系统调用的过程大概如下:
1.陷入内核;
2.加载新的可执行文件并进行可执行性检查;
3.将新的可执行文件映射到当前运行进程的进程空间中,并覆盖原来的进程数据;
4.将EIP的值设置为新的可执行程序的入口地址。
5.返回用户态,程序从新的EIP出开始继续往下执行。至此,老进程的上下文已经被新的进程完全替代了,但是进程的PID还是原来的。
execve系统调用中断上下文的特殊之处在于,新的运行进程中已经找不到原来的对execve调用的代码了,所以execve函数不会成功返回,而是实现了一次完全的变身。
三、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的内核态。
• (4)中断处理过程中或中断返回前调?了schedule函数,其中完成了进程调度算法选择next进程、进程地址空间切换、以及switch_to关键的进程上下?切换等。
• (5)switch_to调?了__switch_to_asm汇编代码做了关键的进程上下?切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程(本例假定为进程Y)的内核堆 栈,并完成了进程上下?所需的指令指针寄存器状态切换。之后开始运?进程Y(这?进程Y曾经通过以上步骤被切换出去,因此可以从switch_to下??代码继续执?)。
• (6)中断上下?恢复,与(3)中断上下?切换相对应。注意这?是进程Y的中断处理过程中,?(3)中断上下?切换是在进程X的中断处理过程中,因为内核堆栈从进程X 切换到进程Y了。
• (7)为了对应起?中断上下?恢复的最后?步单独拿出来(6的最后?步即是7)iret - pop cs:rip/ss:rsp/r?ags,从Y进程的内核堆栈中弹出(3)中对应的压栈内容。此时完 成了中断上下?的切换,即从进程Y的内核态返回到进程Y的?户态。注意快速系统调?返回sysret与iret的处理略有不同。
• (8)继续运??户态进程Y