狭义的中断和异常,均可以被归于广义的异常范畴,因此本文自此将用“异常”作为统一概念进行论述,其包含了狭义上的“中断”和“异常”。
RISC-V 定义的三种模式 User、Supervisor 和 Machine,均可发生异常,但是只有特权模式 Supervisor 和 Machine 才能处理异常,因为处理异常需要 CSR 寄存器。默认情况下,RISC-V所有的异常,都在Machine模式下处理。除此以外,还可以通过委托机制,将异常委托给Supervisor模式下处理。
这里本文介绍的是Machine模式下的异常处理机制。
进入异常时,RISC-V 架构规定的硬件行为,可以简述如下:
(1)停止执行当前程序流,转而从CSR 寄存器mtvec 定义的PC 地址开始执行。
(2)进入异常,不仅会让处理器跳转到,上述的PC 地址开始执行,还会让硬件,同时更新其他几个CSR 寄存器,分别是以下4 个寄存器:
下文将分别予以详述。
RISC-V 架构规定,在处理器的程序执行过程中,一旦遇到异常发生,则终止当前的程序流,处理器被强行跳转到一个新的PC 地址。该过程在RISC-V 的架构中定义为“陷阱(trap)”,字面含义为“跳入陷阱”,更加准确的意译为“进入异常”。
RISC-V 处理器trap 后跳入的PC 地址,由一个叫做机器模式异常入口基地址寄存器mtvec(Machine Trap-Vector Base-Address Register)的CSR 寄存器指定,其要点如下:
(1)mtvec 寄存器是一个可读可写的CSR 寄存器,因此软件可以编程更改其中的值。
(2)mtvec 寄存器的详细格式,如下图所示,其中的最低2 位是MODE 域,高30 位是BASE 域。(32位架构下,XLEN为32;64位架构下,XLEN为64。)
RISC-V 架构规定,在进入异常时,机器模式异常原因寄存器mcause(Machine Cause Register)被同时更新,以反映当前的异常种类,软件可以通过读此寄存器,查询造成异常的具体原因。
mcause 寄存器的详细格式,如下图所示,其中最高1 位为Interrupt 域,低31 位为异常编号域。
此两个域的组合表示值,如下图所示,用于指示RISC-V 架构定义的12 种中断类型和16 种异常类型。
RISC-V 架构定义,异常的返回地址由机器模式异常PC 寄存器mepc(Machine Exception Program Counter)保存。
在进入异常时,硬件将自动更新mepc 寄存器的值,为当前遇到异常的指令PC 值(即当前程序的停止执行点)。该寄存器将作为异常的返回地址,在异常结束之后,能够使用它保存的PC 值,回到之前被停止执行的程序点。
(1)值得注意的是,虽然mepc 寄存器会在异常发生时,自动被硬件更新,但是mepc 寄存器,本身也是一个可读可写的寄存器,因此软件也可以直接写该寄存器以修改其值。
(2)对于狭义的中断和狭义的异常而言,RISC-V 架构定义其返回地址(更新的mepc 值)有些细微差别:
注意:
如果异常由ecall 或ebreak 产生,由于mepc 的值被更新为ecall 或ebreak 指令自己的PC。因此在异常返回时,如果直接使用mepc 保存的PC值作为返回地址,则会再次跳回ecall 或者ebreak指令,从而造成死循环(执行ecall 或者ebreak 指令导致重新进入异常)。正确的做法是,在异常处理程序中,软件改变mepc指向下一条指令,由于现在ecall/ebreak(或c.ebreak)是4(或2)字节指令,因此改写设定 mepc=mepc+4(或+2) 即可。
RISC-V 架构规定,在进入异常时,硬件将自动更新机器模式异常值寄存器mtval(Machine Trap Value Register ),以反映引起当前异常的存储器访问地址或者指令编码。
注意:mtval 寄存器,又名mbadaddr 寄存器,在某些早期版本的RISC-V 编译器中仅识别mbadaddr 名称。
RISC-V 架构规定,在进入异常时,硬件将自动更新机器模式状态寄存器mstatus(Machine Status Register)的某些域。
(1)mstatus 寄存器的详细格式,如上图所示,其中的MIE 域,表示在Machine Mode 下中断全局使能。
(2)RISC-V 架构规定,异常发生时有如下情况。
注意:由于为简化知识模型,在此仅介绍“只支持机器模式”的架构,因此对SIE、UIE、SPP、SPIE 等不做赘述。
当程序完成异常处理之后,最终需要从异常服务程序中退出,并返回主程序。RISC-V架构定义了一组专门的退出异常指令(Trap-Return Instructions),包括MRET、SRET、和URET。
注意:由于为简化知识模型,在此仅介绍“只支持机器模式”的架构,对SRET 和URET 指令不做赘述。
在机器模式下,退出异常时,软件必须使用MRET 指令。
RISC-V 架构规定,处理器执行MRET 指令后的硬件行为,如下:
下文将分别予以详述。
在上文中曾经提及,在进入异常时,mepc 寄存器被同时更新,以反映当时遇到异常的指令的PC 值。通过这个机制,意味着MRET 指令执行后,处理器回到了当时遇到异常的指令的PC 地址,从而可以继续执行之前被中止的程序流。
mstatus 寄存器的详细格式,如上图所示。RISC-V 架构规定,在执行MRET 指令后,硬件将自动更新机器模式状态寄存器mstatus(Machine Status Register)的某些域。
RISC-V 架构规定,执行MRET 指令退出异常时有如下情况:
在上文中曾提及,在进入异常时,MPIE 的值曾经被更新为异常发生前的MIE 值。而MRET 指令执行后,再次将MIE 域的值更新为MPIE 的值。通过这个机制,则意味着MRET指令执行后,处理器的MIE 值被恢复成异常发生之前的值(假设之前的MIE 值为1,则意味着中断被重新全局打开)。
如上文中所述,当处理器进入异常后,即开始从mtvec 寄存器定义的PC 地址执行新的程序。该程序通常为异常服务程序,并且程序还可以通过查询mcause 中的异常编号(Exception Code)决定进一步跳转到更具体的异常服务程序。
譬如当程序查询mcause 中的值为0x2,则得知该异常,是非法指令错误(Illegal Instructions)引起的,因此可以进一步跳转到,非法指令错误异常服务子程序中去。
下图所示为一异常入口程序实例片段,程序通过读取mcause 的值,进而判断异常的类型,从而进入不同的异常服务子程序。
注意:
由于RISC-V 架构规定的进入异常和退出异常机制中没有硬件自动保存和恢复上下文的操作,因此需要软件明确地使用指令进行上下文的保存和恢复。
参考文档: