xv6中trap(即让cpu暂停执行当前代码,去执行相应的处理代码的情况)有三种:
- 系统调用
- 异常,如除以零
- 设备中断,例如完成了一次读写事件,当然也包括定时中断
发生trap时,一般的处理是:
- 控制转移到kernel
- kernel保存当前进程的相关状态(具体就是trampoline.s中,将寄存器保存到进程的trapframe,将栈顶指针设为内核栈的,恢复tp寄存器,让其保存当前cpu的id,从trapframe中取出内核页表页的地址,设置为satp的值,并刷新tlb,从tf中取出usertrap的地址,并跳转到它)
- 执行trap handler代码(usetrap)
- (可选的)执行usertrapret(TODO),
- 执行trampoline.s中的userret
- 返回用户态,继续执行用户程序
上面usertrap和usertrapret有一些细节需要注意,TODO
riscv trap machinery
riscv有一些控制寄存器,xv6里用到的有这些:这些寄存器只能在supervisor 模式(即内核态)被读写
- stvec:保存trap handler的地址,当发生trap时,将他的值设为pc
- sepc:当trap发生时,将pc保存到sepc,trap返回用户态时(trampoline.s中的useret),执行sret将sepc设置为pc
- scause:发生trap时,这里保存的值指示trap的种类/原因
- sscratch:发生trap时,他的值是当前进程tf的地址
- sstatus:其中SPP bit标识之前的模式是supervisor(1)还是用户态(0),SIE bit标识当前中断是否开启,如果为0,那么中断会被推迟到SIE bit为1
当发生trap时,riscv的硬件会:
- 如果是设备中断,并且SIE为0,那么什么也不做
- 禁用中断(SIE置零
- 将pc复制到sepc
- 设置SPP bit,表明当前模式
- 设置scause,表明trap原因
- 将模式设为supervisor
- 设置pc为sepc
- 从新pc开始执行
cpu必须一次完成上述步骤,例如,如果没有更换pc,那么可能现在是内核态,但是仍然执行用户指令
注意,cpu并没有切换页表,没有切换栈,没