中断异常

RISC-V中断异常机制与CSR特权寄存器体系

Posted by CloudingYu on April 1, 2026

一、中断与异常的基本概念

1.1 广义异常——”干活被打断”

从最宽泛的定义出发:不论是中断还是异常,本质都是”正在干活,突然有人打断”。这可以理解为最广义的异常(Exception)。

1.2 中断 vs 异常的区分

在狭义上,两者有所不同:

类型 中文 英文 来源 举例
中断 中断 Interrupt 外部 鼠标点击、键盘输入、打印机、外部设备请求
异常 异常 Exception 内部 非法指令、未对齐访存、除零错误

“中断”听着很明白——干活被打断了;”异常”的意思就是异于常态——该正常的事情不正常了(如读了一条无法解析的指令)。

1.3 中断的主要来源

三类常见中断:

  1. 外部中断:鼠标、键盘、打印机等外部设备发出请求
  2. 内部中断(定时器):计时器定时发出请求
  3. 软件中断:程序主动触发(如 syscalltrap),主动告诉系统”我干好了,你来处理”

1.4 Trap的概念

Trap(陷阱/陷入)——中文有时译为”陷阱”或”陷入”。Trap 是由异常或中断引起的控制转移,跳到特定的处理程序去执行。


二、中断处理方式的两种策略

2.1 向量中断 vs 非向量中断

方式 机制
向量中断(Vectored) 知道中断原因后,直接查表跳转到对应的处理程序入口
非向量中断(Non-vectored) 所有中断先跳到同一个固定入口,然后在软件中判断原因再分发

RISC-V 中选择了非向量方式(更简单)。

2.2 向量中断的计算方式

如果使用向量方式:

\[\text{入口地址} = \text{base} + \text{cause} \times 4\]

其中 cause 是中断/异常原因编号,乘 4 是因为每条入口占 4 字节(一个指针大小)。


三、CSR 寄存器与特权模式

3.1 什么是 CSR

CSR(Control and Status Register,控制与状态寄存器)是 CPU 中一类特殊的寄存器,用于控制和反映 CPU 的状态。与通用的 32 个寄存器不同,CSR 的访问受到权限控制。

3.2 三种特权模式

RISC-V 定义了三种特权级别:

模式 全称 权限 说明
M-mode Machine Mode 最高 机器模式,所有资源全部可见,什么都能做
S-mode Supervisor Mode 中等 监管模式(操作系统内核)
U-mode User Mode 最低 用户模式,只能使用受限的指令和寄存器

当前做的 CPU 就是 M-mode——所有东西都看得见、随便用。但最终跑操作系统时,一定要有 S-mode 和 U-mode 的隔离。操作系统切换不是简单的设个 flag 就跳来跳去,它包含特殊指令和特殊寄存器,只有 M-mode 才能看到。

3.3 关键 CSR 寄存器

最重要的几个 CSR(以 M-mode 为例):

寄存器 全称 功能
mstatus Machine Status 机器状态寄存器,包含 MIE、MPIE 等控制位
mepc Machine Exception PC 保存发生异常时当前指令的 PC 值
mcause Machine Cause 记录导致 trap 的原因编码
mtvec Machine Trap Vector 存放 trap 处理程序的入口地址(base address)
mip Machine Interrupt Pending 记录哪些中断正在等待处理
mie Machine Interrupt Enable 控制哪些中断可以被响应
mscratch Machine Scratch 临时保存寄存器,用于上下文切换

3.4 mstatus 中的关键位

mstatus 中的几个关键位:

  • MIE(Machine Interrupt Enable):全局中断使能位——当前是否允许中断
  • MPIE(Machine Previous Interrupt Enable):保存进入 trap 之前的中断使能状态
  • MPP(Machine Previous Privilege):保存进入 trap 之前的特权模式

为什么需要 MPIE?因为在 trap 处理的第一件事就是关中断(MIE=0),但”进来之前”是什么状态必须记住。回来的时候要从 MPIE 恢复,而不是简单地”设为开”——之前是关的就恢复为关。

3.5 mvendorid / marchid 等标识寄存器

CPU 中还有硬件标识类寄存器(如 mvendorid、marchid),编译器在生成代码时需要读取这些信息来配置编译参数。


四、中断处理的核心流程

4.1 硬件自动完成的步骤

当异常/中断发生时,硬件自动(atomically)完成以下操作:

  1. 保存 PC:将当前指令的 PC 值保存到 mepc
  2. 记录原因:将 trap 的原因编码写入 mcause
  3. 保存并关闭中断:将当前 MIE 值保存到 MPIE,然后将 MIE 置 0(关中断)
  4. 设置特权模式:将当前特权模式写入 MPP,然后切换到 M-mode
  5. 跳转到处理程序:将 PC 设置为 mtvec 中存放的地址

这五件事必须原子地完成——要么全做,要么全不做。就像去银行存钱:把钱给柜员和记账这两件事必须一起完成,不能只做一半。

4.2 原子性的重要性

原子操作(atomic operation)的核心是——这些事要干就干完,干不完就一件别干。以后学数据库时,ACID 四个基本原则中的 A(Atomicity,原子性)就是这个概念。

4.3 软件部分的处理(ISR)

硬件完成后,进入中断服务程序(Interrupt Service Routine, ISR):

  1. 关中断(已由硬件完成)
  2. 保存上下文(Context Save):将当前寄存器压入栈中(和函数调用类似)
  3. 读取 mcause:判断是 interrupt 还是 exception,以及具体原因
  4. 执行对应的处理代码
  5. 恢复上下文(Context Restore):从栈中恢复寄存器
  6. 执行 MRET 指令:硬件恢复 MIE ← MPIE,跳回 mepc 指向的地址

4.4 中断服务程序的第一件事

中断服务程序的第一件事反而是关中断(MIE=0)。因为马上要做重要的事情(保存现场、压栈),这个时候不能让任何人来打扰。

4.5 完整的硬件+软件流程图

1
2
3
异常触发 → 硬件保存 mepc/mcause/mstatus → 跳转 mtvec →
软件保存上下文 → 读 mcause 分派 → 执行处理代码 →
恢复上下文 → MRET → 回到原程序

4.6 MRET 指令

MRET(Machine Return)是退出 trap 的特殊指令:

  • 将 MIE 恢复为 MPIE 的值
  • 将 PC 恢复为 mepc 的值
  • 恢复之前的特权模式

为什么不用内存保存这些状态?因为本身就是几个小标志位,放内存里太慢了,直接放在 CSR 寄存器里硬件自动处理最方便。


五、中断嵌套

5.1 嵌套中断的问题

正在处理一个中断时,又来了新的中断请求,这就是中断嵌套(Nested Interrupt)。

5.2 处理策略

  • 如果不允许嵌套:保持 MIE=0 直到当前 ISR 执行完
  • 如果允许嵌套:在 ISR 完成关键操作(如保存上下文)后,重新打开中断(MIE=1),允许高优先级中断抢占

在 lab 中通常不会涉及复杂的嵌套,了解概念即可。


六、定时器中断

6.1 定时器的工作原理

RISC-V 的定时器由两个关键寄存器组成:

  • mtime:一个 64 位的计数器,每个时钟周期自动 +1
  • mtimecmp:比较寄存器,当 mtime >= mtimecmp 时触发定时器中断

6.2 定时器的使用

  • mtime 从 0 开始计数,不断增长
  • 软件在 mtimecmp 中设置期望的时间点
  • 当 mtime 达到 mtimecmp 的值时,产生中断
  • 中断处理程序中更新 mtimecmp 为下一个时间点

6.3 关于时间的澄清

mtime 记录的并不是”北京时间”或”真实世界时间”——它只是从系统启动开始的 clock 计数。要知道真实时间,需要操作系统在启动时从一个外部时间源(如 RTC)初始化。在 25MHz 或 50MHz 的时钟下,64 位计数器可以运行非常长时间才溢出。


七、CSR 指令集

7.1 专用指令

RISC-V 为 CSR 操作提供了专用指令:

指令 操作
CSRRW CSR Read and Write — 原子地读取 CSR 值到 rd,并将 rs1 的值写入 CSR
CSRRS CSR Read and Set — 读取 CSR 值,并按 rs1 的位掩码置位
CSRRC CSR Read and Clear — 读取 CSR 值,并按 rs1 的位掩码清零
CSRRWI CSR Read and Write Immediate — 使用立即数代替 rs1
CSRRSI CSR Read and Set Immediate
CSRRCI CSR Read and Clear Immediate

7.2 CSRRW 的双操作特性

CSRRW 不是一件事,是两个操作——先读出 CSR 的旧值到 rd,再把新值从 rs1 写入 CSR。这是原子完成的。

7.3 中断处理代码示例

典型的中断处理汇编代码结构:

# 使用 mscratch 保存 a0
csrrw a0, mscratch, a0    # 交换 a0 和 mscratch
# 保存其他寄存器到栈
sw a1, 0(a0)
sw a2, 4(a0)
sw a3, 8(a0)
sw a4, 12(a0)
# ... 保存更多寄存器 ...
# 读取 mcause 判断原因
csrr a1, mcause
# 根据原因分发处理
# ...
# 恢复寄存器
lw a4, 12(a0)
lw a3, 8(a0)
lw a2, 4(a0)
lw a1, 0(a0)
csrrw a0, mscratch, a0    # 恢复 a0
mret                       # 返回

八、中断/异常的 Cause 编码

8.1 mcause 的结构

mcause 寄存器的最高位表示类型:

  • 最高位 = 1:Interrupt(中断)
  • 最高位 = 0:Exception(异常)

低位的 code 字段表示具体原因,例如:

  • code = 3:Breakpoint(断点异常)
  • code = 7:Machine Timer Interrupt(定时器中断)

8.2 向量方式中的 code 使用

如果用向量中断方式,根据 code 计算偏移:

\[\text{入口} = \text{mtvec\_base} + \text{code} \times 4\]

非向量方式则直接跳到 mtvec 存放的固定地址。


九、CPU 性能分析与优化方法论

9.1 优化一定要找准目标

费这么大劲从指令集上优化,提升好少。做了个很厉害的乘法器,提高十倍——但如果程序中乘除法占比只有 5%,整体性能也就提升那么一点点。

核心原则:永远要找到合适的算法,把优化用到最需要的地方。大模型时代之前,普通程序里乘除法占比 30-40%,优化效果不是特别明显。但大模型规模太大,什么方法都不够用——这种情况下所有的优化手段必须全上。

9.2 相同功能不同实现的性能差异

两个 CPU 都能做加减乘除四种操作,但同一个程序编译出来,指令的比例却可能大不相同。原因在于:

  • 如果 CPU 某条指令(如乘法)执行很慢,编译器可能用加法循环来替代
  • 这导致不同 CPU 上程序的指令比例完全不同
  • 衡量性能要看整体执行时间,不能只看单一指令的速度

十、核心主线总结

中断和异常机制围绕以下层面逐步展开:

  1. 概念层:什么是中断、什么是异常,两者的区别与联系
  2. 机制层:向量中断 vs 非向量中断,RISC-V 选择非向量方式
  3. 硬件层:CSR 寄存器体系(mstatus、mepc、mcause、mtvec)、特权模式(M/S/U)
  4. 流程层:硬件自动操作 → 软件 ISR 处理 → MRET 返回,完整的 trap 处理生命周期
  5. 细节层:原子性要求、中断嵌套、定时器中断原理、CSR 指令集
  6. 实践层:CPU 性能计算公式、优化目标选择

CPU 优化不是炫技——把除法器做快十倍挺厉害,但你的程序里除法只占 5%。永远先找到最关键的热点,把智力发挥到最适合的地方。