xv6-riscv trap 源码详解 2
现在我们进入了usertrap函数,在这里面进行中断的处理操作,不是关注的重点我们就跳过了。直接看usertrapret函数。

从第一个intr_off()语句上的注释能看出,先关闭中断是为了防止嵌套中断改变sstatus寄存器和c寄存器的状态,而导致不能正确返回用户态。intr_off()实际上是将SIE置0,禁止监督模式处理中断。
将trap陷入地址重新写成uservec的地址保存在stevc中,返回user-mode后遇到trap能重新进入uservec处理trap。
保存内核上下文:
- satp 保存了 kernel pagetable 地址
- sp 保存内核栈指针
- kernel_trap 保存usertrap函数入口地址
- kernel_hartid 保存内核线程id
介绍下sstatus寄存器:
- 0 位
UIE(user interrupt enable)用户模式中断使能,开启后用户模式能直接处理中断 - 1 位
SIE(supervisor interrupt enable)监督模式中断使能,开启后监督模式能处理中断(xv6中都是监督模式处理中断,异常) - 5 位
SPIE(supervisor previous interrupt enable)上一个监督模式中断使能 - 8 位
SPP(supervisor previous privilege)上一个特权级,如果之前是user-mode,SPP=0; 如果之前是s-mode, SPP=1。
将SPP设置为0 将SPIE置1 , 这两步在执行sret后起作用。 (执行sret后,如果SPP=0,则返回到User-mode;如果SPP=1,则返回到S-mode;将SPIE中的置给到SIE,实现进入user-mode,并打开我们上面关闭s-mode的中断使能;清除SPIE和SPP的值,置0)
将user-mode遇到trap时的地址写入 epc 寄存器,执行sret后,pc将会等于epc值。
user pgtable 给到变量 satp 。从这里也可以看到进程的user-pagetable 是单独保存在进程结构体中的;而kernel-pagetable是保存在进程结构体中的trapframe结构体中。
调用函数fn(),函数入口地址是userret,两个参数TRAPFRAME, satp被保存到a0,a1, 进入userret。
接下来我们看看userret。

第一条指令切换成 user-pagetable
ld t0, 112(a0) #a0是trapframe的地址 112(a0)是trapframe->a0
csrw sscratch, t0
上面ld指令将trapframe->a0的值存入t0中
csrw指令将 t0值写入sscratch。合起来就是将trapframe->a0写入sscratch
下面就是恢复trapframe中保存的用户寄存器状态:

注意到a0仍保存的trapframe地址,而sscratch保存着trapframe->a0,下面先交换这两个值。保证下次进入时,sscratch中保存着trapframe地址。
csrrw a0,sscratch,a0
sret
sert执行后,硬件执行的操作:
-
恢复特权级: 根据 SPP(Supervisor Previous Privilege)字段的值,将特权级恢复到异常发生前的状态: 如果 SPP = 0,切换到用户模式(U-mode)。 如果 SPP = 1,保持在监督模式(S-mode)。
-
恢复中断使能状态: 将 SPIE(Supervisor Previous Interrupt Enable)的值恢复到 SIE(Supervisor Interrupt Enable): 如果 SPIE = 1,则重新使能 S-mode 中断(SIE = 1)。 如果 SPIE = 0,则禁止 S-mode 中断(SIE = 0)。
-
清除状态位: 将 SPP 和 SPIE 清零,为下一次异常或中断做准备。
-
跳转到返回地址: 从 sepc(Supervisor Exception Program Counter)寄存器中加载返回地址,并跳转到该地址继续执行。