正在执行的用户态X切换用户态进程Y的过程为系统中常用的情况,但并非不能完全准确地反应系统的全部执行场景,还有一些场景比较特殊,主要包括以下5种情况
一.内核线程之间通过中断处理过程中的调度时机发生进程切换,与一般的情况非常类似,只是内核线程在运行过程中发生中断,没有进程用户态和内核态的切换
二.用户进程向内核线程的切换。比一般的情况更简单,内核线程不需要从内核态返回用户态,如果该内核是直接调用schedule函数主动让出cpu的,那么它被重新调度执行时就没有中断上下文恢复现场的问题
三.内核线程向用户线程的切换。如果内核线程主动调用schedule函数,只用进程上下文的切换,没有发生中断上下文切换,它比一般的情况也更简单,但用户进程从内核态返回用户态依然需要中断上下文恢复现场返回用户态
四.创建的子进程第一次执行时的执行起点较为特殊,需要人为地创建一个进程上下文环境作为起始点。比如fork一个进程时,子进程不是从schedule函数中完成进程cpu关键上下文切换之后开始执行的,而是从ret_from_fork开始执行的:
1. 子进程执行的上下文构造
- 内核栈初始化:
fork
创建子进程时,通过copy_thread
函数复制父进程的内核栈,并手动设置子进程的指令指针(eip/rip)为ret_from_fork
,而非父进程被中断时的地址。
- 寄存器状态伪造:子进程的寄存器上下文(如
eax
)被设置为0(表示子进程的fork返回值),其他寄存器从父进程复制,形成“伪中断返回”环境。
2. ret_from_fork
的作用
中断模拟入口:该标签是内核中断返回的通用路径,子进程通过此处模拟从中断返回用户态的过程,完成以下操作:
- 恢复用户态寄存器(通过
restore_all
) - 执行开中断操作(因进程切换时中断默认关闭)
- 跳转到用户态指定地址(通常为
fork
后的下一条指令)。
与schedule
的差异:schedule
负责进程切换时的资源调度,而ret_from_fork
是子进程首次获得CPU时的执行起点,无需经历完整的调度器上下文切换
五、加载一个新的可执行程序execve系统调用返回用户态的情况也较为特殊,需要人为地创建一个中断上下文的现场。比如execvex系统调用加载新的可执行程序,在execve系统调用处理过程修改了触发该系统调用保存的中断上下文现场,使得返回用户态的位置修改为新程序的elf_entry或者ld动态链接器的起点地址
1. 用户态程序主动调用
-
Shell执行命令
当用户在终端输入命令(如ls
或./a.out
)时,Shell(如bash
)会通过fork()
创建子进程,并在子进程中调用execve
加载目标程序。
Shell → fork() → 子进程 → execve("/bin/ls", ...) → 替换为ls进程
通过分析总结,大致可以想象出Linux操作系统的一般执行过程过程,其中的关键点如下:
1. 中断和中断返回有中断上下文的切换,也就是保存现场和恢复现场,cpu和内核代码中断处理程序入口的汇编语言代码结合起来完成中断上下文的切换
2.进程调度过程中有进程上下文的切换,而此切换完全由内核完成,具体包括:从一个进程的地址空间切换到另一个进程的地址空间;从一个进程的内核堆栈切换到另一个进程的内核堆栈;还有进程的cpu上下文关键上下文切换
linux内核通过中断上下文切换和进程上下文这两种基本的运行机制来保障为用户提供最基本和最重要的服务,这些服务如下:
1、通过系统调用的形式为进程提供各种服务
2、通过中断服务程序为I/O、内存管理等硬件的正常工作提供各种服务
3、通过内核线程为系统提供动态的维护服务,以及完成中断服务中可延时处理的任务