RISC-V 特权指令集入门

13,337 阅读9分钟

CSR

CSR 是支撑 RISC-V 特权指令集的一个重要概念。CSR 的全称为 控制与状态寄存器(control and status registers)。

简单来说,CSR 是 CPU 中的一系列特殊的寄存器,这些寄存器能够反映和控制 CPU 当前的状态和执行机制。在 RISC-V 特权指令集手册 中定义的一些典型的 CSR 如下:

  • misa,反映 CPU 对于 RISC-V 指令集的支持情况,如 CPU 所支持的最长的位数(32 / 64 / 128)和 CPU 所支持的 RISC-V 扩展。
  • mstatus,包含很多与 CPU 执行机制有关的状态位,如 MIE 是否开启 M-mode 中断等。

CSR 的地址空间有 12 位,因此理论上能够支持最多 4,096 个 CSR。但实际上,这个地址空间大部分是空的,RISC-V 手册中实际只定义了数十个 CSR。访问不存在的 CSR 将触发无效指令异常。关于该地址空间的分配,请参考 RISC-V 特权指令集手册的 2.2 CSR Listing 节。

操作 CSR 的指令在 RISC-V 的 Zicsr 扩展模块中定义。包括伪指令在内,共有以下 7 种操作类型:

  1. csrr,读取一个 CSR 的值到通用寄存器。如:csrr t0, mstatus,读取 mstatus 的值到 t0 中。
  2. csrw,把一个通用寄存器中的值写入 CSR 中。如:csrw mstatus, t0,将 t0 的值写入 mstatus
  3. csrs,把 CSR 中指定的 bit 置 1。如:csrsi mstatus, (1 << 2),将 mstatus 的右起第 3 位置 1。
  4. csrc,把 CSR 中指定的 bit 置 0。如:csrci mstatus, (1 << 2),将 mstatus 的右起第 3 位置 0。
  5. csrrw,读取一个 CSR 的值到通用寄存器,然后把另一个值写入该 CSR。如:csrrw t0, mstatus, t0,将 mstatus 的值与 t0 的值交换。
  6. csrrs,读取一个 CSR 的值到通用寄存器,然后把该 CSR 中指定的 bit 置 1。
  7. csrrc,读取一个 CSR 的值到通用寄存器,然后把该 CSR 中指定的 bit 置 0。

这些指令都有 R 格式和 I 格式,I 格式的指令名需要在 R 格式的指令名之后附加字母 i,如 R 格式指令 csrr 对应的 I 格式指令为 csrri。具体的指令格式和执行机制请参考 RISC-V 非特权指令手册 的 Zicsr 节。

前 4 种操作 csrr / csrw / csrs / csrc 是伪指令,这些指令会由汇编器翻译成对应的 csrrw / csrrs / csrrc 指令。这样做是为了减少 CPU 需要实现的指令数量,使 CPU 的片上面积利用更高效。具体请参考 RISC-V 非特权指令集手册 的 RISC-V Assembly Programmer’s Handbook 节。

mscratch CSR 中读出并写入一个值的示例汇编代码如下:

csrr	t0, mscratch
addi	t0, t0, 1
csrw	mscratch, t0

四种特权模式

类似于 x86 中的特权模式,RISC-V 特权指令集中也定义了 4 种特权模式(参考 RISC-V 特权指令集手册的 1.2 Privilege Levels 节)。它们的名字和代号如下:

  • Machine mode (M-mode),序号为 3;
  • Hypervisor mode (H-mode),序号为 2;
  • Supervisor mode (S-mode),序号为 1;
  • User mode (U-mode),序号为 0。

其中,权限最高的模式为 M-mode,它拥有对机器底层的一切访问。通常来说,M-mode 中运行的代码必须是可信代码。M-mode 可以用于管理机器内的安全执行环境(手册原文:secure execution environments)。

H-mode 用于尚未完成的 RISC-V 硬件虚拟化(hypervisor)扩展。虚拟机管理程序将运行在该特权模式下。在现行(2019 年 6 月)的 RISC-V 特权指令集手册稳定版本中,该特权模式是保留(reserved)的。

相应地,S-mode 和 U-mode 分别划分给操作系统和用户程序使用。

通常,一个 CPU 需要实现 M,S,U 三种模式,才能够运行完整的类 Unix 操作系统(如 Linux)。

与 x86 不同,RISC-V 并没有提供一种机制来获取当前的特权模式。这是因为 RISC-V 设计者认为,一段代码在编写的时候,就应该能知道它的目标特权模式是什么,因此在代码中手动获取当前特权模式是没有必要的。设计这样一种机制会造成 CSR 状态位冗余。

RISC-V 规范中只提供了一种在不同特权模式间切换的方法,即借助中断返回机制。具体请参考下一章节。

中断和异常

在 RISC-V 特权指令集手册中,能够引起当前程序中断,并使得 CPU 转而执行特定代码的事件统称为 陷阱(trap)。陷阱共分为两大类:中断(interrupt)和 异常(exception),其中:

  • 中断 通常指由外部设备引发的事件(当然,也有一类中断是软件中断),具有 异步处理 的特点;
  • 异常 指的是 CPU 内部在执行程序时产生的异常事件,具有 同步处理 的特点。

(注:以上分类存在一定争议,如 OSDev.org 就对以上的名词有 不同解释,认为 陷阱 指的是在产生异常时,CPU 同步地跳转到异常处理程序的动作。以上分类仅代表作者本人观点。)

RISC-V 中的 中断 又分为 3 种主要类型:

  • 软件中断,指不是由外部设备引起的,而是通过软件写入 mip CSR 触发的中断。
  • 时钟中断
  • 外部中断,指受 RISC-V 平台级中断控制器(Platform Level Interrupt Controller,PLIC)控制的,由外部设备触发的中断。

不将时钟中断归类为外部中断的原因是,时钟中断不受 PLIC 的控制,而是由两个独立的寄存器 mtimemtimecmp 控制。时钟中断也是唯一一种由外部设备引发的,且在 RISC-V 特权指令集手册中有明确定义的中断。

除时钟中断之外,由外部设备引发的中断都由 RISC-V 的 PLIC 控制。PLIC 负责捕获中断请求,按优先级顺序发送给 CPU;并向 CPU 提供一系列的硬件寄存器,供程序查询中断相关的信息。

(注 2:下文中仍根据 x86 的约定,将 RISC-V 的中断和异常统称为 中断,RISC-V 中的中断称为 外部中断。)

RISC-V 中断处理程序

在 RISC-V CPU 中,外部中断必须通过 CSR 手动开启,否则 CPU 不会接收到任何中断(除了 non-maskable interrupts,不可屏蔽中断)。但 CPU 异常只要在异常条件满足时就会触发,并且无法通过 CSR 屏蔽。

开启中断共需要经过两个步骤,其中 mstatus[MIE] 是中断总开关(MIE 是 machine interrupt enabled 的缩写),mie CSR 是针对每种中断类型的独立开关。只有当两个 CSR 都正确设置时,才能够触发中断。

中断响应程序的地址需要放在 mtvec CSR 中。目前 RISC-V 支持两种类型的中断向量:

  • 直接模式(direct),所有类型的中断均发送给同一个中断响应程序。
  • 向量化模式(vectored),外部中断 将根据 中断类型 发送给不同的中断响应程序,但所有的 异常 仍然发送给 同一个 异常响应程序。

当有中断发生时,CPU 会做(包括但不限于)以下工作:

  • 发生异常的指令被中断时的下一条指令 的 PC 地址放入 mepc CSR;
  • 将中断类型码放入 mcause CSR;
  • 如果中断带有附加信息(如:非法内存访问时的内存地址),则该信息将放入 mtval CSR;
  • 如果是外部引发的中断,令 mstatus[MPIE] = mstatus[MIE],并令 mstatus[MIE] = 0,相当于在进入中断响应程序前暂时关闭中断。PIE 是 previous interrupt enabled 的缩写。
  • 将当前特权模式代号放入 mstatus[MPP] 中,并将当前模式设为 M-mode;
  • 根据 mtvec CSR 的值,决定中断响应程序的地址,并跳转到该地址。

默认情况下,所有的中断均在 M-mode 下处理,无论当前的特权模式是什么。但 M-mode 也可以配置中断委托(delegation),将部分中断直接转发给低权限的模式,如 S-mode 和 U-mode。这时,相应的 CSR 的名称则变为 sepc / uepc 等。有关中断委托的细节,可以参考 RISC-V 特权指令集手册的 3.1.8 Machine Trap Delegation Registers (medeleg and mideleg) 节。

M-mode 的中断响应程序通过 mret 指令从中断中返回。该指令会进行以下操作:

  • 将当前特权模式设置为 mstatus[MPP]
  • mstatus[MIE] = mstatus[MPIE],以还原发生中断前的中断开关;
  • mstatus[MPIE] = 1
  • mstatus[MPP] 将被设为 U(如果 CPU 不支持 U-mode,则设为 M);
  • 将 PC 的值设为 mepc 的值,以返回中断前的程序。

具体流程请参考 RISC-V 特权指令集手册的 3.1.6.1 Privilege and Global Interrupt-Enable Stack in mstatus register 和 3.2.2 Trap-Return Instructions 节。

其它模式下的中断,及跳转到低权限模式的方法

S-mode 下的中断和中断返回的机制与 M-mode 有所不同,不详细介绍。具体请参考 RISC-V 特权指令集手册的 4 Supervisor-Level ISA 章节。

RISC-V 标准中提供的唯一一种从 M-mode 跳转到低权限模式的方法是:将 mstatus[MPP] 设为要跳转的模式,将 mepc 设为跳转的目标地址,然后执行 mret

可由平台定义的部分

RISC-V 标准给予了芯片设计者很大的自由发挥的空间。如:

  • 物理内存属性(physical memory attributes, PMAs):RISC-V 的内存地址空间并不一定要与物理内存一一对应。芯片设计者可以自行决定芯片的物理内存布局,称为 物理内存属性(PMA)。
  • CPU 重置(reset):CPU 启动后的 PC 地址可以由芯片设计者定义。
  • 不可屏蔽中断(non-maskable interrupts, NMI):RISC-V 标准中没有定义任何具体的 NMI 类型,可以由芯片设计者自行定义。NMI 的中断响应程序地址也是由芯片设计者自行定义的。
  • 调试模式(debug mode, D-mode):拥有比 M-mode 更高的权限,供芯片调试使用。具体行为可由芯片设计者定义。