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 语言处理函数。
核心修正点总结
- 硬件动作:硬件不负责“进入跳板”,它只负责跳到
stvec。跳板是内核提前写好的代码。 - 交换内容:
csrrw交换的是 **用户a0**和trapframe的指针,而不是页表地址。页表地址是随后从trapframe里读出来再写进satp的。 - 跳板原理:跳板之所以不需要“无页表执行”,是因为它在两张页表中都有相同位置的映射(Same Virtual Address),实现了平滑过渡。