最近了解了一波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始终找不到当前运行模式的标志。

image.png

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)
  }

折腾了半天,没想到这运行模式保存在软件不可见的硬件中。

标签: riscv, 特权级

添加新评论