启动第一个进程
概要:梳理 xv6 启动第一个进程的完整流程:调度器选定 initproc、内核线程切换(swtch)、通过 forkret 与 trapret 从内核态返回用户态,最终运行 initcode。
1. 准备与调度阶段 (Scheduler)
在 main 函数完成初始化后,控制权交给调度器。
- 寻找目标:
scheduler遍历进程表,找到状态为RUNNABLE的第一个进程initproc。 - 环境切换:
- 将当前 CPU 的
proc指针指向该进程。 - 调用
switchuvm:切换硬件页表(由于内核映射相同,此处切换不影响内核运行),并设置 TSS (Task State Segment),确保中断发生时能正确压入内核栈。
- 将当前 CPU 的
- 状态标记:将进程状态由
RUNNABLE修改为RUNNING。
2. 内核线程切换 (swtch)
这是从“调度器线程”到“进程内核线程”的跳转。
- 上下文保存:调用
swtch,将当前调度器的硬件寄存器保存到cpu->scheduler中。 - 上下文载入:将
initproc->context中保存的寄存器(如%esp和%eip)加载到硬件中。 - 执行流跳转:随着
ret指令执行,处理器从栈中弹出目标进程的%eip。此时,处理器已正式运行在initproc的内核栈上。
3. 从内核态返回用户态 (Forkret & Trapret)
这是最精妙的部分,利用模拟的中断返回机制进入用户空间。
- forkret 衔接:
ret首先跳转到forkret。它负责执行一些必须在进程上下文中进行的初始化。 -
trapret 恢复:
forkret返回后,根据allocproc预先设计的栈结构,下一个弹出的地址是trapret。- 弹出 Trap Frame:
trapret使用pop类指令从栈中恢复通用寄存器(popal)和段寄存器(gs,fs等)。 - 跳过异常码:跳过
trapno和errcode。 - iret 终章:执行
iret指令,硬件自动弹出%cs,%eip,%flags,%esp,%ss。
- 弹出 Trap Frame:
4. 运行用户代码 (Initcode)
此时,处理器正式进入用户模式。
- 入口地址:
%eip指向虚拟地址 0(initcode.S的起点),%esp指向 4096。 - 地址翻译:分页硬件通过
allocuvm建立的页表,将虚拟地址 0 映射到实际分配的物理内存。 - 权限约束 (Isolation):
- PTE_U 标志:页表项设置了用户权限位,允许 CPL=3 的程序访问。
- 特权级隔离:由于
userinit将%cs的低位设为 3,处理器运行在用户态,无法执行修改%cr3等敏感寄存器的特权指令,确保了系统的安全。
核心流程总结表
| 阶段 | 核心函数 | 关键动作 | 结果 |
|---|---|---|---|
| 调度 | scheduler |
选定 initproc,切换页表 |
环境准备就绪 |
| 切换 | swtch |
交换寄存器组,执行 ret |
进入进程内核栈 |
| 返回 | trapret |
弹出 Trap Frame,执行 iret |
跨越内核边界进入用户态 |
| 执行 | initcode |
在虚拟地址 0 处运行 | 运行第一个用户指令 |
留下评论