RISCV 特权级拾遗
最近了解了一波RISCV特权指令,起因是想弄清楚linux启动过程中M模式、S模式、U模式分别是如何切换的?中断和异常的时候硬件做了什么,软件要做什么?如何判断当前运行在什么模式下?
1. 模式转换
带着这些问题查看手册,确实收获不少,比如:
- 开机时,默认进入M模式。初始化之后,配置mstatus寄存器MPP位,执行
mret
指令可以跳转到S或U模式。 - U模式下主动执行
ecall
(同步异常),可以进入M模式,跳转到预先保存在mtvec的异常处理程序地址,最后通过mret
返回U模式。 - U模式下被动接受的中断包括,plic(键盘鼠标等),clic(时钟中断,线程切换必备),软件中断。流水线检测到中断信号时,会调整到预先保存在mtvec的异常处理程序地址,最后通过
mret
返回U模式。
详细的可以参考中文手册第10章
2. 硬件做了什么?
直接上图,但有一点疑惑,最后“再把权限模式更改为M”,我在gdb始终找不到当前运行模式的标志。
3. 判断当前模式
在qemu下运行简单的OS,在M和U模式下执行csrr mhartid
,发现运行前的M寄存器都一样,但U模式会陷入异常。
在一篇问答里找到一点线索:
RISC-V故意不让代码轻易发现它正在运行的模式,因为这是一个虚拟化漏洞。作为一般原则,代码应该为哪种模式设计,并隐式知道它将在哪种模式下运行。应用程序代码应假定它处于 U 模式。操作系统应该假设它处于S模式(它实际上可能是虚拟化的,并在U模式下运行,U模式无法执行虚拟机监控程序捕获和模拟的事情)。
最后查看Nutshell的源码(rocketchip看不来),在src/main/scala/nutcore/backend/fu/CSR.scala
文件找到
// 367行定义了特权级寄存器,初始化为M模式,该寄存器用户不可见!
val priviledgeMode = RegInit(UInt(2.W), ModeM)
//618行根据不同模式下的ecall指令
csrExceptionVec(ecallM) := priviledgeMode === ModeM && io.in.valid && isEcall
csrExceptionVec(ecallS) := priviledgeMode === ModeS && io.in.valid && isEcall
csrExceptionVec(ecallU) := priviledgeMode === ModeU && io.in.valid && isEcall
//659行 Mret 更新模式为mpp的值
when (valid && isMret) {
val mstatusOld = WireInit(mstatus.asTypeOf(new MstatusStruct))
val mstatusNew = WireInit(mstatus.asTypeOf(new MstatusStruct))
// mstatusNew.mpp.m := ModeU //TODO: add mode U
mstatusNew.ie.m := mstatusOld.pie.m
priviledgeMode := mstatusOld.mpp
mstatusNew.pie.m := true.B
mstatusNew.mpp := ModeU
mstatus := mstatusNew.asUInt
lr := false.B
retTarget := mepc(VAddrBits-1, 0)
}
折腾了半天,没想到这运行模式保存在软件不可见的硬件中。