少于 1 分钟阅读 次阅读

概要:梳理 xv6 启动第一个进程的完整流程:调度器选定 initproc、内核线程切换(swtch)、通过 forkret 与 trapret 从内核态返回用户态,最终运行 initcode。

1. 准备与调度阶段 (Scheduler)

main 函数完成初始化后,控制权交给调度器。

  • 寻找目标scheduler 遍历进程表,找到状态为 RUNNABLE 的第一个进程 initproc
  • 环境切换
    • 将当前 CPU 的 proc 指针指向该进程。
    • 调用 switchuvm:切换硬件页表(由于内核映射相同,此处切换不影响内核运行),并设置 TSS (Task State Segment),确保中断发生时能正确压入内核栈。
  • 状态标记:将进程状态由 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 Frametrapret 使用 pop 类指令从栈中恢复通用寄存器(popal)和段寄存器(gs, fs 等)。
    • 跳过异常码:跳过 trapnoerrcode
    • iret 终章:执行 iret 指令,硬件自动弹出 %cs, %eip, %flags, %esp, %ss

4. 运行用户代码 (Initcode)

此时,处理器正式进入用户模式。

  • 入口地址%eip 指向虚拟地址 0initcode.S 的起点),%esp 指向 4096
  • 地址翻译:分页硬件通过 allocuvm 建立的页表,将虚拟地址 0 映射到实际分配的物理内存。
  • 权限约束 (Isolation)
    • PTE_U 标志:页表项设置了用户权限位,允许 CPL=3 的程序访问。
    • 特权级隔离:由于 userinit%cs 的低位设为 3,处理器运行在用户态,无法执行修改 %cr3 等敏感寄存器的特权指令,确保了系统的安全。

核心流程总结表

阶段 核心函数 关键动作 结果
调度 scheduler 选定 initproc,切换页表 环境准备就绪
切换 swtch 交换寄存器组,执行 ret 进入进程内核栈
返回 trapret 弹出 Trap Frame,执行 iret 跨越内核边界进入用户态
执行 initcode 在虚拟地址 0 处运行 运行第一个用户指令

留下评论