一、中断与异常的基本概念
1.1 广义异常——”干活被打断”
从最宽泛的定义出发:不论是中断还是异常,本质都是”正在干活,突然有人打断”。这可以理解为最广义的异常(Exception)。
1.2 中断 vs 异常的区分
在狭义上,两者有所不同:
| 类型 | 中文 | 英文 | 来源 | 举例 |
|---|---|---|---|---|
| 中断 | 中断 | Interrupt | 外部 | 鼠标点击、键盘输入、打印机、外部设备请求 |
| 异常 | 异常 | Exception | 内部 | 非法指令、未对齐访存、除零错误 |
“中断”听着很明白——干活被打断了;”异常”的意思就是异于常态——该正常的事情不正常了(如读了一条无法解析的指令)。
1.3 中断的主要来源
三类常见中断:
- 外部中断:鼠标、键盘、打印机等外部设备发出请求
- 内部中断(定时器):计时器定时发出请求
- 软件中断:程序主动触发(如
syscall、trap),主动告诉系统”我干好了,你来处理”
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)完成以下操作:
- 保存 PC:将当前指令的 PC 值保存到 mepc
- 记录原因:将 trap 的原因编码写入 mcause
- 保存并关闭中断:将当前 MIE 值保存到 MPIE,然后将 MIE 置 0(关中断)
- 设置特权模式:将当前特权模式写入 MPP,然后切换到 M-mode
- 跳转到处理程序:将 PC 设置为 mtvec 中存放的地址
这五件事必须原子地完成——要么全做,要么全不做。就像去银行存钱:把钱给柜员和记账这两件事必须一起完成,不能只做一半。
4.2 原子性的重要性
原子操作(atomic operation)的核心是——这些事要干就干完,干不完就一件别干。以后学数据库时,ACID 四个基本原则中的 A(Atomicity,原子性)就是这个概念。
4.3 软件部分的处理(ISR)
硬件完成后,进入中断服务程序(Interrupt Service Routine, ISR):
- 关中断(已由硬件完成)
- 保存上下文(Context Save):将当前寄存器压入栈中(和函数调用类似)
- 读取 mcause:判断是 interrupt 还是 exception,以及具体原因
- 执行对应的处理代码
- 恢复上下文(Context Restore):从栈中恢复寄存器
- 执行 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 上程序的指令比例完全不同
- 衡量性能要看整体执行时间,不能只看单一指令的速度
十、核心主线总结
中断和异常机制围绕以下层面逐步展开:
- 概念层:什么是中断、什么是异常,两者的区别与联系
- 机制层:向量中断 vs 非向量中断,RISC-V 选择非向量方式
- 硬件层:CSR 寄存器体系(mstatus、mepc、mcause、mtvec)、特权模式(M/S/U)
- 流程层:硬件自动操作 → 软件 ISR 处理 → MRET 返回,完整的 trap 处理生命周期
- 细节层:原子性要求、中断嵌套、定时器中断原理、CSR 指令集
- 实践层:CPU 性能计算公式、优化目标选择
CPU 优化不是炫技——把除法器做快十倍挺厉害,但你的程序里除法只占 5%。永远先找到最关键的热点,把智力发挥到最适合的地方。