第2章:RISC-V特权架构基础:M模式、S模式、U模式与中断委托机制
各位同学,咱们今天聊聊RISC-V的特权架构。说实话,这部分内容我当年刚接触时也绕了不少弯路。特权级这个概念,说白了就是给CPU划分了几个不同的“权限等级”。你想想看,一个操作系统总不能允许用户程序随便改控制寄存器吧?那不乱套了。
2.1 三种特权模式概述
RISC-V定义了三种特权模式,从高到低分别是:
- 机器模式(M-mode):最高权限,硬件复位后默认进入的模式
- 监督模式(S-mode):操作系统内核运行的模式
- 用户模式(U-mode):应用程序运行的模式
嗯,这里要注意,并不是所有RISC-V实现都必须支持全部三种模式。嵌入式场景下,很多MCU只实现了M-mode,跑裸机程序就够了。但咱们做中断控制器设计,至少得支持M-mode和S-mode,不然没法跑Linux。
核心要点:特权级越高,能访问的资源和控制的权限就越多。M-mode可以访问所有CSR寄存器,S-mode只能访问部分,U-mode基本啥都不能碰。
2.2 机器模式(M-mode)—— 真正的“老大”
M-mode是RISC-V里权限最高的模式。硬件复位后,CPU第一件事就是进入M-mode。我在项目中遇到过一个问题:有次调试时发现中断响应总是不对,查了半天才发现是M-mode下的mtvec寄存器配置错了。这个寄存器指向中断向量表的基地址,一旦写错,中断就飞到不知道哪里去了。
M-mode能访问的CSR寄存器包括:
| 寄存器 | 功能描述 |
|---|---|
| mtvec | 机器模式中断向量表基地址 |
| mstatus | 机器模式状态寄存器,控制全局中断使能 |
| mie | 机器模式中断使能寄存器,控制具体中断源 |
| mip | 机器模式中断挂起寄存器,显示哪些中断正在等待 |
| mcause | 记录中断或异常的原因 |
| mepc | 发生中断时保存的返回地址 |
我个人习惯,在写底层启动代码时,第一件事就是初始化mtvec。你想想看,如果中断向量表都没设好,后面所有中断处理都是空中楼阁。
2.3 监督模式(S-mode)—— 操作系统的地盘
S-mode是给操作系统内核用的。Linux、FreeRTOS这些系统,它们的核心代码就跑在S-mode下。S-mode有自己的CSR寄存器集,比如stvec、sie、sip等,功能和M-mode的对应寄存器类似,但权限低一级。
为什么需要S-mode?我举个例子:假设用户程序想访问硬盘,它不能直接操作硬件,得通过系统调用陷入内核。内核在S-mode下处理这个请求,然后通过ecall指令返回用户态。这套机制保证了系统的稳定性和安全性。
避坑指南:我曾经在移植Linux时,发现S-mode下的中断使能位没设置对。具体来说,sie寄存器里的SSIE位(软件中断使能)必须置1,否则核间中断(IPI)根本发不出去。这个坑我踩了整整两天才爬出来。
2.4 用户模式(U-mode)—— 应用程序的沙盒
U-mode是权限最低的模式。应用程序跑在这里,不能直接访问硬件,不能修改CSR寄存器,甚至连关中断的资格都没有。说白了,U-mode就是个“沙盒”,程序在里面随便折腾,但别想碰到底层的东西。
U-mode下如果试图执行特权指令,CPU会触发异常,陷入S-mode或M-mode处理。这就是操作系统的“保护机制”——用户程序崩溃了,顶多杀掉这个进程,不会影响整个系统。
2.5 中断委托机制 —— 把中断“下放”给S-mode
好,重点来了。中断委托机制是RISC-V里一个非常巧妙的设计。你想想看,如果所有中断都跑到M-mode去处理,那操作系统还有什么意义?每次中断都要经过M-mode转一手,性能损失太大了。
RISC-V的解决方案是:通过mideleg和medeleg这两个CSR寄存器,把一部分中断和异常“委托”给S-mode处理。
// 委托定时器中断给S-mode
csrw mideleg, (1 << 5) // 第5位对应STI(监督模式定时器中断)
// 委托外部中断给S-mode
csrw mideleg, (1 << 9) // 第9位对应SEI(监督模式外部中断)
// 委托异常给S-mode(比如ecall)
csrw medeleg, (1 << 8) // 第8位对应环境调用异常(U-mode下的ecall)
委托之后,这些中断和异常会直接由S-mode处理,不再经过M-mode。我在设计中断控制器时,通常会把外部中断和定时器中断都委托给S-mode,这样Linux内核可以直接管理这些中断源,效率高得多。
重要提醒:委托机制不是万能的。有些中断必须由M-mode处理,比如不可屏蔽中断(NMI)和硬件错误中断。这些中断如果委托出去,系统可能无法及时响应致命错误。我建议你只委托那些你确定S-mode能正确处理的中断。
2.6 模式切换与中断响应流程
咱们把整个流程串起来看看:
- CPU在U-mode下跑应用程序,突然来了个外部中断
- 硬件检查mideleg寄存器,发现这个中断被委托给了S-mode
- CPU自动切换到S-mode,保存当前PC到sepc寄存器
- 跳转到stvec指向的中断向量表,执行中断处理程序
- 处理完成后,执行mret指令(注意,这里用的是mret而不是sret,因为最终返回时需要M-mode协助)
嗯,这里有个细节:虽然中断被委托给了S-mode,但返回时用的指令是mret而不是sret。为什么?因为mret指令会从M-mode的CSR寄存器中恢复状态,而sret只恢复S-mode的状态。实际实现中,中断处理程序最后会通过一条特殊的指令序列完成返回。
2.7 实际项目中的经验总结
最后,我分享几点实际项目中的经验:
- 初始化顺序很重要:先配M-mode的mtvec,再配S-mode的stvec,最后才开全局中断。顺序反了,中断来了没地方去。
- 委托要谨慎:不是所有中断都适合委托。我一般只委托外部中断和定时器中断,软件中断和异常留给M-mode处理。
- 调试时多用mstatus寄存器:这个寄存器里的MIE、SIE、UIE位分别控制各模式的中断使能。调试时看一眼mstatus,就知道中断被关在哪一层了。
好了,这一章的内容就到这里。特权架构是RISC-V中断控制器的基石,理解透了,后面讲PLIC和CLINT时你会觉得轻松很多。下一章咱们聊聊中断控制器的具体硬件实现,到时候我会拿一个实际项目的代码来讲解。