RISC-V 陷阱处理程序重入陷阱处理程序中的异常

RISC-V trap handler reentrancy on an exception in the trap handler

系统初始化时将mscratch设置为trap handler内存上下文,然后执行似乎是常用的技巧:

csrrw tp, CSR_MSCRATCH, tp

在机器模式陷阱处理程序的开头交换 tp 和 mscratch。 然后在机器模式陷阱处理程序中使用 tp 来访问陷阱处理程序本身所需的 memory/state。在陷阱处理程序结束时,再次执行此操作以恢复指向 mscratch 的内存上下文指​​针和 tp 的原始值。

不过好像有点问题。如果由于某种原因陷阱处理程序本身发生异常,您将交换 tp 的原始值,该值现在位于 mscratch 中,具有机器模式陷阱处理程序上下文,因此在处理陷阱时您将使用错误的内存上下文处理程序生成异常。

如果您想检测这种情况,您需要一个免费寄存器来读取 MPP。

如何处理?

我有一个支持嵌套异常的玩具处理程序。我总是1scratch csr 中保留指向当前 运行ning 代码的上下文块的指针——这包括在异常处理程序本身的执行期间。

我的玩具管理员是这样开始的:

csrrw t0, scratch, t0   # swap interrupted t0 and its context block pointer
beqz t0, bootUp         # or if it is null then put the base CBP in there...
sw t1, 12(t0)           # save interrupted t1 to its context block
lw t1, 4(t0)            # fetch context block pointer for me
csrrw t1, scratch, t1   # t1 has interrupted t0, CSR scratch points to my context block
sw t1, 8(t0)            # save interrupted t0 to its context block
sw t2, 16(t0)           # save t2

这段代码之后,t0是指向中断代码上下文块的指针,被中断的t0t1t2被保存到中断代码的上下文中块,因此 t1t2 可以自由使用,并且 scratch csr 是指向异常处理程序的此 运行 的上下文块的指针。

用户代码的上下文块可以存储在任何地方,例如malloc根据需要编辑。在每个用户上下文块中(例如上面的 4(t0)),我存储了一个指向特殊异常处理程序上下文块数组的指针。

在任何异常处理程序上下文块中(在特殊数组中),每个(在偏移量 4 处)都被预初始化以引用数组中的下一个条目,因此嵌套异常自然会在特殊数组中采用不同的上下文块数组,像动态嵌套异常处理程序的堆栈一样工作;因此,对于该数组中的尽可能多的元素,它可以处理许多嵌套异常。

如果需要,用于异常处理程序的上下文块可以包含其他数据,例如指向全局数据的指针。


处理快速中断可能会节省一些寄存器,运行 一些代码,恢复这些寄存器并恢复中断的代码。对于其他一些中断,调度程序可以选择切换上下文(例如 I/O 就绪),恢复与上次中断不同的线程。由于适当的上下文块已经具有中断线程的部分状态,对于完整的上下文切换,仅需要保存额外的寄存器状态(无需将任何寄存器状态从临时位置传输到适当的上下文块)。


1 除了处理程序开头的 ~4-5 指令 window。


作为恢复的一部分,对于正在恢复的任何线程,都会将其 t0 及其上下文块指针恢复到 scratch csr。

如果遇到嵌套异常,要恢复的线程只是被中断的异常处理程序,而如果用户线程被中断,则由调度程序决定是否恢复最近中断的用户线程(更便宜通常不需要完整的上下文切换)或恢复另一个先前挂起的用户线程,该线程可能已被时间片超时或 I/O 系统调用挂起。如果外部中断指示已完成 I/O 操作,则等待该 I/O 的线程可能适合恢复。 (系统调用,我正在做 csrrw zero, %arg, zero,以便在陷阱指令中编码系统调用号,并且避免使用 ecall,因为 RARS 服务于此)。

在我的玩具系统中,我支持用户级别的多线程,因此一个线程可以通过系统调用请求“操作系统”创建另一个线程。因此,当异常处理程序上下文块堆叠在该预初始化数组中时,用户线程的上下文块可以被“malloc”。

我的玩具系统运行在 RARS 下,所以是的,它没有多个地址空间的复杂性。