RISC V::中断与异常处理 -- 异常篇

一般在修读 Operating System 时,都会学习到 Interrupt 的概念,此外,电脑在运作时也会碰到大大小小的问题。你可曾好奇电脑是如何排除这些问题的呢?
在本篇文章中,笔者会向大家介绍 RISC-V 处理器是如何处理中断以及异常问题的。

RISC-V 拥有不同的特权模式,供不同层级的程序运作在对应的平台上,如: Machine ModeUser ModeSupervisor Mode 等等。而本系列文只会专注在具有完整硬体存取权限的 Machine Mode 上,如果读者对特权模式有兴趣,可以参考 RISC-V 的官方文件: riscv-privileged-v1.10

CSR 暂存器

RISC-V 架构定义了许多暂存器,部分暂存器被定义为控制和状态暂存器,也就是标题所指出的 CSR (Control and status registers),它被用於配置或是纪录处理器的运作状况。
笔者先将 RISC-V 架构中 (Machine Mode) 与中断、异常有关的暂存器都列出来,方便待会儿进行解说:

  • CSR
    • mtvec
      当进入异常时,PC (Program counter) 会进入 mtvec 所指向的地址并继续运行。
    • mcause
      纪载异常的原因。
    • mtval
      纪载异常讯息。
    • mepc
      进入异常前 PC 所指向的地址。若异常处理完毕,Program counter 可以读取该位址并继续执行。
    • mstatus
      进入异常时,硬体会更新 mstatus 寄存器的某些域值。
    • mie
      决定中断是否被处理。
    • mip
      反映不同类型中断的等待状态。
  • Memory Address Mapped
    • mtime
      纪录计时器的值。
    • mtimecmp
      储存计时器的比较值。
    • msip
      产生或结束软件中断。
    • PLIC

Exception

我们都知道,处理器是藉由接收指令进行运作的,若处理器在执行指令流的时候遇到无法预期的情况,便称之为异常

其实异常与中断非常相似,最主要的差别是异常发生於硬体、程序上的故障。

异常的类型

异常主要分成两大类型:

同步异常

要判断异常属於同步或是异步最简单的方式是: 在同样的程序以及环境执行 n 次都能将同样的异常状态重现,该异常就可以被归类到同步异常。常见状况有:

  • 非法指令产生的错误
  • 在 Debugger 设置断点
  • 撷取指令时访问到非法的地址空间
  • 访问地址的属性出现错误
  • 存取指令地址时产生的非对齐错误

异步异常

异步异常又能在被细分为两种状态: 精确的异步异常以及不精确的异步异常
由於简体文字书籍对於精确/非精确异步异常的介绍相当模糊。因此,笔者直接整理下表方便大家厘清:

异常 描述
精确的异步异常 外部中断
不精确的异步异常 读写内存时出错

 !读写内存时出错说明:
 常见在当处理器将资料写进快取後,等到该资料在快取中被替换(存回外部记忆体)时才发现错误,此时处理器已经又处理了上百万条指令。在这个状况中,我们很难找出罪魁祸首(出错的指令)是谁。因此,该状况被归类在不精确的异步异常

异常处理

1. Trap

当异常发生时,处理器会停止手边的工作,再将 Program counter 的位址指向 mtvec 所指的位址并开始执行。这样的行为就好像是主动跳入陷阱一样,因此,在 RISC-V 的架构中将这个动作定义为 Trap

补充: mtvec 是一个可读可写的暂存器,开发者可以透过软件修改其值。

  • 当 MODE 为 0 时,转跳至 BASE 值表示的位址。
  • 当 MODE 为 1 时(同步异常),会将 PC 的位址指向:
    BASE + 4 X CAUSE
    !这边的 CAUSE 值等同於 mcause register 所表示的值。

2. 更新 CSR Register (mcause)

在 RISC-V 架构中有定义:
当进入异常时硬体会更新 mcause register 的值,开发者可以透过软件去读取 mcause 以分析造成异常的具体原因。

mcause 的最高位 (MSB) 用来记录是否为 interrupt,其余 31 位都用来表示异常编号。

3. 更新 CSR Register (mepc)

mepc register 被用来存放跳入 Trap 前 PC 指向的指令位址,待异常处理结束後,PC 会将 mepc 存放的指令位址读入并开始执行。
需要注意的是,mepc 在中断和异常发生时会有不同的行为:

  • 异常发生时
    异常发生时,mepc 会记录 Program Counter 指向的指令位址,也就是出现异常的那条指令。
  • 中断发生时
    当中断发生时,mepc 会记录 Program Counter 指向的位址的下一个指令位址,例如:
    1. 中断发生! (PC = 0x1c)
    2. mepc = PC + 4;
    3. 异常处理
    4. 处理完成,跳回正常状态 (PC = mepc)
  • 特例
    若异常是由 ecall 或是 ebreak 造成的,则参照中断发生时的情况处理。否则会造成 ecall 以及 ebreak 重复呼叫形成无穷回圈。

    关於 ecall 以及 ebreak,请参考 RV32I 指令集。
    补充: mepc register 也是一个可读可写的暂存器。因此,我们同样可以利用软件修改它的值。

4. 更新 CSR Register (mtval)

mtval register 又称为 mbadaddr register,在 RISC-V 的规格书有定义:

当进入异常时,硬体会自动更新 mtval register 的值,以纪录造成异常发生的暂存器访问地址或是指令编码。

从上面的叙述就可以知道,mtval 会有两种写入的 Case,分别是:

  • 暂存器访问造成的异常
    包括如硬件断点、存取指令、储存器读写时造成的异常。这时,硬体会将储存器访问的地址记录到 mtval 当中。

  • 非法指令造成的异常

    除了 RVC 指令集外,其他合法 RISC-V 指令集的 OPCODE 末两码都是 11。

    在这个情况中,硬体会将非法的指令编码记录到 mtval 当中。

5. 更新 CSR Register (mstatus)

mstatus 纪载了大量的资料,根据 RISC-V 架构的规定:
当进入异常时,硬体会自动更新 mstatus 的某些域值。

format of mstatus

  • MIE
    当 MIE 域的值为 1 时为 Enable,反之为 Disable。

简单来说,就是决定处理器要不要受理中断请求。

  • MPIE
    MPIE 用来存放异常发生前 MIE 域的值。当异常结束後就可以利用 MPIE 还原 MIE 的值。

  • MPP
    纪录异常发生前的工作模式,在 RISC-V 规格书中,有以下几种模式:

    Level Encoding Name Abbreviation
    0 00 User/Application U
    1 01 Supervisor S
    2 10 Reserved -
    3 11 Machine M

6. 退出异常状态

当异常处理完成後,需要从异常服务退出。在 RISC-V 架构中定义了一组用於退出异常的指令 (Trap-Return Instruction),包括:

  1. MRET
  2. SRET
  3. URET

分别对应了 Machine Mode, Supervisor Mode 以及 User Mode。
使用 MRET 指令退出异常後,硬体会做两件事情:

  1. 从 mepc 指向的指令位址开始执行

  2. 更新 mstatus register
    同样以 Machine Mode 为例:

    • 将 MIE 更新为 MPIE 的值。
    • 将 MPIE 域的值更新为 1。

    需要注意的是: MIE 域仅是反映中断是否接受处理,其控制权仍取决於 MIE register 中的 MEIE 域。

总结

在先前导读过的 rv32emu-next 专案中,也包含了异常的处理,如果有兴趣可以在自行阅读原始码。经过上面的介绍後,就能轻松的看懂这个 RISC-V 的 Emulator 是如何运作的了(吗?)

Reference


<<:  Day 06 Python 的特点

>>:  【7】Dataset 的三个API : Shuffle Batch Repeat 如果使用顺序不同会产生的影响

[Day 27]粗糙集特徵选择简介-5

上次说到了在特徵子集 Q 之下 被特徵子集 P 细分的程度 如果是 1 则表示 P 可以完全分类 Q...

iOS APP 开发 OC 第十五天,网路请求(请求方式对比,缓存策略,请求时长)

tags: OC 30 day 获取网路数据的两种方式: 方式一:NSData 方法: 获取JSON...

【Day26】[演算法]-快速排序法Quick Sort

快速排序法(Quick Sort)又称分割交换排序法,是目前公认效率极佳的演算法,使用了分治法(Di...

Day 10 : PHP - 常用的阵列函数有哪些?

上篇介绍了PHP的阵列宣告、印出方式,这篇想和大家介绍PHP常用的阵列函数有哪些 1.in_arra...

第18天~SharedPreference常被使用於资料储存

SharedPreference常被使用於资料储存,很适合做一些简单的资料存取 先配置按钮-因为是要...