【前言】
RISC-V 的异常与中断入口不像 ARM Cortex-M 那样有固定的向量表硬件分发,一切都要靠 CSRs 自己接管。本文把 mcause / mtvec / mepc / mstatus 四个核心寄存器的配合逻辑梳理成一条可落地的处理链。
一、几个关键 CSR 各自负责什么
1. mtvec:异常入口地址与模式选择。低两位为 MODE,00=Direct 表示所有异常跳同一入口,01=Vectored 表示中断跳 BASE+4*cause,异常仍跳 BASE。
2. mcause:最高位为 1 表示中断,为 0 表示异常;低位是 cause 编号,需要区分同步异常与异步中断的处理分支。
3. mepc:保存被中断指令的地址,mret 时回到这里继续执行。
4. mstatus:MPIE 与 MIE 两位决定进入陷阱时是否关中断,以及 mret 后是否恢复。
二、标准的处理流程
1. 进入陷阱时硬件自动完成:mepc 等于当前 PC,mcause 等于原因,mstatus.MPIE 等于 mstatus.MIE,mstatus.MIE 清零,PC 跳 mtvec.BASE。
2. 软件入口第一件事是保存现场,通用寄存器加 mstatus 都要保存,建议汇编手写上下文保存,不要依赖编译器。
3. 读 mcause 判断分支:最高位为 1 走中断分支,为 0 走异常分支。
4. 异常处理里注意 mepc 可能需要加 4 修正,例如 ECALL 场景,否则 mret 回去会死循环。
5. mret 前恢复现场,mstatus.MIE 由 mret 自动从 MPIE 恢复。
三、最常见的三个坑
1. mtvec 未对齐:Vectored 模式下 BASE 必须 4 字节对齐,且要保证表项落在正确地址上。
2. 忘记清中断源:外设中断在 PLIC 或 CLINT 侧有 pending 位,只清 MEIP 不清外设会反复进中断。
3. 中断里调用不可重入函数:printf、malloc 在中断上下文里是雷区。
四、中断嵌套的实现要点
RISC-V 默认关中断进入陷阱,要实现嵌套需要在保存现场后手动把 mstatus.MIE 置 1,并设置中断优先级屏蔽。嵌套深度建议限制在 2 到 3 层,否则栈消耗不可控。
五、调试建议
把 mcause、mepc、mtval 三个寄存器在入口处一并打印出来,异常定位效率会明显提升;mtval 对非法指令、访存错误能直接给出出错地址或指令编码。
以上是基于 RISC-V 特权规范与常见国产 RISC-V MCU 的实践总结,欢迎各位补充自己踩过的坑。 |
|