riscv异常处理

riscv 中断模式

riscv 有三种中断模式:

M 模式 S 模式
时钟中断 CLINT 的计时器比较 mip.STIP
软件中断 CLINT 的寄存器 mip.SSIP
外部中断 PLIC 的中断线 PLIC 的中断线或 mip.SEIP

CLINT

一种较为简单的设备:

寄存器 特征
mtime 可读可写,以恒定速率增加
mtimecmp 可读可写,当 mtime>=mtimecmp 时产生 M 模式中断
msip 可读可写,写入 1 则触发 M 模式中断

PLIC

当有多个设备同时发起外部中断,PLIC 负责从中选择一个,并通过 M 模式外部中断通知 CPU。

CPU 的异常处理过程:

八个控制状态寄存器(CSR)是 M 模式下异常处理的必要部分:

简称 全称 作用
mtvec Machine Trap Vector 它保存发生异常时处理器需要跳转到的地址。
mepc Machine Exception PC 它指向发生异常的指令。
mcause Machine Exception Cause 它指示发生异常的种类。
mie Machine Interrupt Enable 它指出处理器目前能处理和必须忽略的中断。
mip Machine Interrupt Pending 它列出目前正准备处理的中断。
mtval Machine Trap Value 它保存了陷入(trap)的附加信息:地址例外中出错的地址、发生非法指令例外的指令本身,对于其他异常,它的值为 0。
mscratch Machine Scratch 它暂时存放一个字大小的数据。
mstatus Machine Status 它保存全局中断使能,以及许多其他的状态。

M 模式的时钟中断有以下几个条件:

  1. 处理器在 M 模式下运行(显然的)
  2. 全局中断使能位 mstatus.MIE = 1(只有在全局中断使能位 mstatus.MIE 置 1 时才会产生中断。)
  3. mie[7] = 1(每个中断在控制状态寄存器 mie 中都有自己的使能位。M 模式下是 mie[7]。)
  4. mip[7] = 1(同上)

如果这些条件同时满足,则可以处理机器的时钟中断。

机器的时钟中断

当一个 hart 发生异常时,硬件自动经历如下的状态转换:

  • 异常指令的 PC 被保存在 mepc 中,PC 被设置为 mtvec。(对于同步异常,mepc 指向导致异常的指令;对于中断,它指向中断处理后应该恢复执行的位置。)
  • 根据异常来源设置 mcause(如图 10.3 所示),并将 mtval 设置为出错的地址或者其它适用于特定异常的信息字。
  • 把控制状态寄存器 mstatus 中的 MIE 位置零以禁用中断,并把先前的 MIE 值保留到 MPIE 中。
  • 发生异常之前的权限模式保留在 mstatus 的 MPP 域中,再把权限模式更改为 M。(如果处理器仅实现 M 模式,则有效地跳过这个步骤)。

用伪代码来表示:

1
2
3
4
1. mepc = pc, pc = mtvec;
2. mcause = Exception; // 参见下表
3. MPIE = mstatus.MIE, mstatus.MIE = 0;
4. mstatus.MPP = Mode, Mode = M;
Interrupt Exception Code Description
1 0 Reserved
1 1 Supervisor software interrupt
1 2 Reserved
1 3 Machine software interrupt
1 4 Reserved
1 5 Supervisor timer interrupt
1 6 Reserved
1 7 Machine timer interrupt
1 8 Reserved
1 9 Supervisor external interrupt
1 10 Reserved
1 11 Machine external interrupt
1 12–15 Reserved
1 ≥16 Designated for platform use
0 0 Instruction address misaligned
0 1 Instruction access fault
0 2 Illegal instruction
0 3 Breakpoint
0 4 Load address misaligned
0 5 Load access fault
0 6 Store /AMO address misaligned
0 7 Store /AMO access fault
0 8 Environment call from U-mode
0 9 Environment call from S-mode
0 10 Reserved
0 11 Environment call from M-mode
0 12 Instruction page fault
0 13 Load page fault
0 14 Reserved
0 15 Store /AMO page fault
0 16–23 Reserved
0 24–31 Designated for custom use
0 32–47 Reserved
0 48–63 Designated for custom use
0 ≥64 Reserved

为避免覆盖整数寄存器中的内容,中断处理程序先在最开始用 mscratch 和整数寄存器(例如 a0)中的值交换。通常,软件会让 mscratch 包含指向附加临时内存空间的指针,处理程序用该指针来保存其主体中将会用到的整数寄存器。在主体执行之后,中断程序会恢复它保存到内存中的寄存器,然后再次使用 mscratcha0 交换,将两个寄存器恢复到它们在发生异常之前的值。最后,处理程序用 mret 指令(M 模式特有的指令)返回。mretPC 设置为 mepc,通过将 mstatus 的 MPIE 域复制到 MIE 来恢复之前的中断使能设置,并将权限模式设置为 mstatusMPP 域中的值。这基本是前一段中描述的逆操作。

此模式的基本时钟中断处理程序只对时间比较器执行了递增操作,然后继续执行之前的任务。更实际的时钟中断处理程序可能会调用调度程序,从而在任务之间切换。它是非抢占的,因此在处理程序的过程中中断会被禁用。不考虑这些限制条件的话,它就是一个只有一页的 RISC-V 中断处理程序的完整示例!
有时需要在处理异常的过程中转到处理更高优先级的中断。唉,mepcmcausemtvalmstatus 这些控制寄存器只有一个副本,处理第二个中断的时候如果软件不进行一些帮助的话,这些寄存器中的旧值会被破坏,导致数据丢失。可抢占的中断处理程序可以在启用中断之前把这些寄存器保存到内存中的栈,然后在退出之前,禁用中断并从栈中恢复寄存器。
除了上面介绍的 mret 指令之外,M 模式还提供了另外一条指令:wfi(Wait ForInterrupt)。wfi 通知处理器目前没有任何有用的工作,所有它应该进入低功耗模式,直到任何使能有效的中断等待处理,即 mie&mip ≠ 0。RISC-V 处理器以多种方式实现该指令,包括到中断待处理之前都停止时钟。有的时候只把这条指令当作 nop 来执行。因此,wfi 通常在循环内使用。

wfi 不论全局中断使能有效与否都有用。
如果在全局中断使能有效(mstatus.MIE = 1)时执行 wfi,然后有一个使能有效的中断等待执行,则处理器跳转到异常处理程序。另一方面,如果在全局禁用中断时执行 wfi,接着一个使能有效的中断等待执行,那么处理器继续执行 wfi 之后的代码。这些代码通常会检查控制状态寄存器 mip,以决定下一步该做什么。与跳转到异常处理程序相比,这个策略可以减少中断延迟,因为不需要保存和恢复整数寄存器。