Skip to content

RISC-V 陷阱机制

每个 RISC-V CPU 都有一组控制寄存器,内核通过写入这些寄存器来告诉 CPU 如何处理陷阱,内核也可以读取这些寄存器来了解已发生的陷阱。RISC-V 文档包含了完整的故事 [riscv:priv]。riscv.h 包含了 xv6 使用的定义。以下是最重要寄存器的纲要:

  • stvec: 内核在此处写入其陷阱处理程序的地址;RISC-V 跳转到 stvec 中的地址来处理陷阱。
  • sepc: 发生陷阱时,RISC-V 在此处保存程序计数器(因为 pc 随后会被 stvec 中的值覆盖)。sret(从陷阱返回)指令将 sepc 复制到 pc。内核可以写入 sepc 来控制 sret 的去向。
  • scause: RISC-V 在此处放置一个数字,描述陷阱的原因。
  • sscratch: 陷阱处理程序代码使用 sscratch 来帮助其在保存用户寄存器之前避免覆盖它们。
  • sstatus: sstatus 中的 SIE 位控制是否启用设备中断。如果内核清除 SIE,RISC-V 将延迟设备中断,直到内核设置 SIE。SPP 位指示陷阱是来自用户模式还是监督者模式,并控制 sret 返回到哪种模式。

上述寄存器与在监督者模式下处理的陷阱有关,不能在用户模式下读取或写入。

多核芯片上的每个 CPU 都有自己的一组这些寄存器,并且在任何给定时间可能有多个 CPU 在处理陷阱。

当需要强制陷阱时,RISC-V 硬件对所有陷阱类型执行以下操作:

  1. 如果陷阱是设备中断,并且 sstatus 的 SIE 位被清除,则不执行以下任何操作。
  2. 通过清除 sstatus 中的 SIE 位来禁用中断。
  3. pc 复制到 sepc
  4. sstatus 的 SPP 位中保存当前模式(用户或监督者)。
  5. 设置 scause 以反映陷阱的原因。
  6. 将模式设置为监督者。
  7. stvec 复制到 pc
  8. 在新的 pc 处开始执行。

请注意,CPU 不会切换到内核页表,不会切换到内核中的堆栈,也不会保存除 pc 之外的任何寄存器。内核软件必须执行这些任务。CPU 在陷阱期间只做最少的工作的一个原因是为软件提供灵活性;例如,一些操作系统在某些情况下省略页表切换以提高陷阱性能。

值得思考的是,上面列出的步骤中是否有任何一个可以省略,也许是为了追求更快的陷阱。虽然在某些情况下更简单的序列可以工作,但通常省略许多步骤是危险的。例如,假设 CPU 没有切换程序计数器。那么来自用户空间的陷阱可以在仍在运行用户指令的情况下切换到监督者模式。这些用户指令可能会破坏用户/内核隔离,例如通过修改 satp 寄存器以指向一个允许访问所有物理内存的页表。因此,CPU 切换到内核指定的指令地址,即 stvec,是很重要的。