ecall与Trap机制
这是从用户态穿越到内核态的物理过程。理解这个过程是理解 Lab 4 (Traps) 的关键。
1. 触发点: ecall 指令
当用户程序调用 write() 时,库函数最终会执行汇编指令 ecall (Environment Call)。
- 硬件行为 (Atomic):
- 如果当前是 User Mode,升级到 Supervisor Mode。
- 保存当前
pc到sepc。 - 保存当前 Trap 原因到
scause。 - 跳转 到
stvec指向的地址 (即uservec)。
2. 上半场: uservec (trampoline.S)
此时,CPU 刚进内核,但页表还是用户的,寄存器还是用户的。除了 sscratch,内核一无所有。
- 交换
sscratch和a0:csrrw a0, sscratch, a0。现在a0指向了 Trapframe,sscratch保存了用户原本的a0。 - 保存用户寄存器: 把所有通用寄存器 (
ra,sp,gp…)sd到 Trapframe 里保存。 - 加载内核环境:
- 从 Trapframe 读取内核栈地址,写入
sp。 - 从 Trapframe 读取内核页表地址,写入
satp(切换页表!)。 - 执行
sfence.vma刷新 TLB。
- 从 Trapframe 读取内核栈地址,写入
- 跳转: 跳到
usertrap()(C语言函数)。
3. 中场: usertrap (trap.c)
C 代码接管控制权。
- 检查
scause。如果是8(Syscall),则处理系统调用。如果是时钟中断,则可能通过yield()切换进程。 - 更新
sepc(对于 Syscall,返回地址应该是ecall的下一条指令,所以sepc += 4)。
4. 下半场: usertrapret & userret
准备回用户态。
usertrapret: 关中断,更新stvec指向uservec(为下一次进来做准备),把内核信息(如内核栈指针)填回 Trapframe。userret(trampoline.S):uservec的逆过程。- 切换回用户页表 (
satp)。 - 从 Trapframe 恢复所有用户寄存器。
- 交换
sscratch和a0。 - 执行
sret指令 → 降级回 User Mode,跳转回sepc。
- 切换回用户页表 (