Trap 整体流程

当用户进程调用 syscall 时,为了与内核交互,控制权必须从用户态转移到内核态。这一过程分为以下几个关键阶段:

1. 触发与硬件响应

用户程序执行 ecall 指令。此时,硬件(CPU)会自动完成以下原子操作

  • 权限提升:将处理器模式从用户态(U-mode)提升到监管者模式(S-mode)。
  • 保存现场指针:将当前的程序计数器(PC)保存到 sepc 寄存器中,以便后续返回。
  • 状态记录:在 scause 寄存器中记录触发陷阱的原因(即系统调用)。
  • 初始跳转:将 PC 跳转到 stvec 寄存器预设的地址,即进入 Trampoline(跳板页)

2. 跳板页的“救急”操作

由于此时所有通用寄存器(a0-a31)都存着用户数据,内核无法直接使用它们。

  • 寄存器交换:执行 csrrw a0, sscratch, a0。这一步极其关键,它将用户寄存器 a0 的值与 sscratch 中预存的 当前进程 trapframe 的物理地址 进行了交换。
  • 保存上下文:现在 a0 成了指向 trapframe 的指针,内核利用这个“支点”,通过一系列 sd 指令将用户所有的通用寄存器(包括暂时寄存在 sscratch 里的原 a0 值)保存到 trapframe 内存块中。

3. 页表切换与连续执行

  • 地址空间切换:现场保存后,跳板代码从 trapframe 中提取内核页表的地址,写入 satp 寄存器。
  • 无缝衔接的原理:之所以能在切换页表的瞬间不崩溃,是因为 Trampoline 在用户页表和内核页表中被映射到了完全相同的虚拟地址。这意味着即使切换了“宇宙(地址空间)”,下一条指令在虚拟内存中的位置依然合法且一致。

4. 进入内核逻辑

  • 跳转至 C 代码:页表切换完成后,跳板代码从 trapframe 中获取内核栈指针(kernel_sp)和 usertrap 函数的地址。
  • 处理逻辑:CPU 跳转到 usertrap(),正式开始执行内核的 C 语言处理函数。

核心修正点总结

  1. 硬件动作:硬件不负责“进入跳板”,它只负责跳到 stvec。跳板是内核提前写好的代码。
  2. 交换内容csrrw 交换的是 **用户 a0**trapframe 的指针,而不是页表地址。页表地址是随后从 trapframe 里读出来再写进 satp 的。
  3. 跳板原理:跳板之所以不需要“无页表执行”,是因为它在两张页表中都有相同位置的映射(Same Virtual Address),实现了平滑过渡。