当前位置: 首页 > news >正文

RISC-V特权模式及切换

1 RISC-V特权模式基本概念

1.1 RISC-V特权模式介绍

RISC-V 指令集架构(ISA)采用多特权级别设计作为其核心安全机制,通过层次化的权限管理实现系统资源的隔离与保护。该架构明确定义了四个层次化的特权模式,按照权限等级由高至低依次为:

  • 机器模式(Machine Mode,M-mode) - 最高特权级别,负责硬件初始化和底层安全控制
  • 虚拟机监控模式(Hypervisor Mode,H-mode) - 可选模式,用于虚拟化扩展
  • 监管者模式(Supervisor Mode,S-mode) - 操作系统内核运行级别,管理系统资源
  • 用户模式(User Mode,U-mode) - 最低权限级别,仅允许用户程序运行

和ARM的EL0-EL3分级类似,RISC-V这种分级特权架构为系统设计提供了灵活的权限控制方案,各模式间通过严格的权限边界实现安全隔离,满足从嵌入式系统到云计算平台的不同安全需求。每个特权级别都配备专属的寄存器组和控制机制,确保执行环境的完整性和安全性。

  1. Machine Mode (M-mode)
    机器模式是RISC-V最高特权级别,可以访问所有内存区域(包括内存、外设和CSR寄存器),执行所有的特权指令和处理所有的中断和异常;一般会在系统启动BootLoader阶段使用。

  2. Hypervisor Mode (H-mode)
    虚拟机模式是RISC-V次高特权级别模式,依赖与硬件虚拟化扩展(H扩展),一般应用在服务器级别,日常实际接触并不多(后面基本不会涉及);该模式支持管理虚拟机的内存和资源和处理虚拟机的异常和中断。

  3. Supervisor Mode (S-mode)
    监督者模式是 中等特权级别的模式,该模式依赖与M-mode的支持,可以访问内核及硬件资源,处理系统调用的部分异常和中断,一般在运行操作系统内核(如Linux)、管理任务调度、管理内存映射(如页表)和控制用户程序等场景下使用。

  4. User Mode (U-mode)
    用户模式是最低特权级别的模式,正常情况下仅访问用户内存区域,允许运行用户程序,无法直接访问硬件或修改系统状态的,也是无法执行CSR特权指令;在触发异常时通过ecall切换到特权模式(如S-mode 或 M-mode)请求内核服务

1.2 常见的设计

常用的RISC-V芯片架构中,M 模式是权限最高的特权等级,也是 RISC-V Spec 中明确规定必须要实现的特权等级,其他三个特权等级是可选的,处理器设计厂商可根据产品的功能定位和成本效益进行选择性实现,在典型的嵌入式应用场景中,对于不需要运行Linux等复杂操作系统的微控制器,为优化功耗控制和芯片面积,往往仅实现Machine模式,或者选择性地实现Machine/User两种模式组,一般情况下:

  • 单片机(运行RTOS):通常支持M模式和U模式,或者只有M模式
  • 通用SOC(运行linux):通常支持M、S、U模式

在一个典型 Linux 系统中,用户态应用程序跑在 U 模式,内核跑在 S 模式,而 M 模式一般是 OpenSBI/U-Boot、Bootloader启动阶段在用。

另外:
RISC-V 手册中有提到过一个 Debug Mode,可以理解为比M模式权限更高的特权等级,用于支持芯片调试。关于这个模式不再这里介绍,想要了解,资料可参考riscv-debug-release.pdf

2 特权模式切换

对于嵌入式工程师来说,对Linux的操作系统更熟悉些,所以为了更好的理解,这里从Linux用户态、内核态角度,对比理解硬件的特权模式。

2.1 Linux操作系统和特权关系

一般来说用户程序都运行在用户态,当它们需要切换到内核态以获得更高权限时,需要向操作系统申请;而操作系统内核和设备驱动程序则默认就运行在内核态。

Linux操作系统的用户态和内核态就分别对应到RISC-V处理器的特权等级就是:用户态对应U-Mode,内核态对应S-Mode。

2.2 Linux用户态、内核态的切换

通常,用户程序只运行在用户态(User Mode),仅在以下三种情况下会切换到内核态(Kernel Mode)进行处理:

  • 系统调用:当应用程序需要访问操作系统服务(如文件读写、进程创建)时,通过 int 指令或 syscall 指令主动请求内核执行操作。
  • 异常:运行时若发生异常(如缺页异常、非法指令、除零错误等),CPU 会自动切换到内核态,由操作系统处理异常。
  • 外部中断:外设(如磁盘、网卡)完成任务后向 CPU 发送中断信号,CPU 根据中断向量表跳转到内核态处理事件。

这种机制通过限制用户程序的直接硬件访问权限,确保系统的安全性和稳定性,仅在必要时通过上述三种方式切换到内核态。

2.3 RISC-V特权模式切换

特权模式切换示意:
在这里插入图片描述

  1. 从高特权级别到低特权级别:

    • 通过异常返回指令(如MRET、SRET)返回到低特权级别
    • 处理器恢复之前保存的状态并切换到低特权级别
  2. 从低特权级别到高特权级别:

    • 通过异常(如系统调用、中断、页错误)或陷阱(trap)进入高特权级别
    • 处理器保存当前状态(如PC、寄存器)并切换到高特权级别

2.3.1 opensbi启动过程特权模式切换

以Linux启动过程中,opensbi阶段特权模式为例。
这里仅保留和切换相关的代码,一般opensbi阶段工作在M模式,完成初始化之后,将切到S模式。

sbi_hart_switch_mode(unsigned long arg0, unsigned long arg1,unsigned long next_addr, unsigned long next_mode,bool next_virt)
{// 1. 配置mstatus寄存器:设置MSTATUS.MPP=S模式,关闭M模式中断unsigned long mstatus = csr_read(CSR_MSTATUS);mstatus = INSERT_FIELD(mstatus, MSTATUS_MPP, next_mode); // MSTATUS.MPP = S模式mstatus = INSERT_FIELD(mstatus, MSTATUS_MPIE, 0);       // 禁用M模式中断csr_write(CSR_MSTATUS, mstatus);// 2. 设置mepc为S模式代码入口地址csr_write(CSR_MEPC, next_addr);// 3. 设置S模式相关寄存器csr_write(CSR_STVEC, next_addr);  // 设置S模式异常入口csr_write(CSR_SSCRATCH, 0);       // 清零S模式临时寄存器csr_write(CSR_SIE, 0);                             //关闭S模式中断使能csr_write(CSR_SATP, 0);                          //禁用S模式的分页机制// 4. 向a0/a1传递参数(例如设备树地址或启动参数)register unsigned long a0 asm("a0") = arg0;register unsigned long a1 asm("a1") = arg1;// 5. 执行mret切换到S模式,跳转到next_addr__asm__ __volatile__("mret" : : "r"(a0), "r"(a1));__builtin_unreachable();
}

这里再来简单看下流程

  1. ​​配置 mstatus​​:
    • 将 mstatus.MPP 设置为 PRV_S,表示 mret 后切换到S模式。
    • 关闭M模式中断(mstatus.MPIE = 0),确保切换时中断处于禁用状态。
  2. ​​设置 mepc​​:
    • 将目标S模式代码的入口地址写入 mepc,mret 后CPU跳转至此地址。
  3. 设置S模式相关寄存器
    • 设置S模式异常入口CSR_STVEC
    • 清空S模式临时寄存器
    • 关闭S模式中断使能(防止内核启动时被打断)
    • 禁用S模式分页机制
  4. 执行 mret​​:
    • CPU从M模式退出,特权级降为S模式。
    • 跳转到 mepc 指定的地址执行代码。
    • 通过 a0 和 a1 寄存器传递启动参数。

另外,Linux内核特权模式切换,在内核起来之后,就会拉起用户态程序,这是就需要把特权模式从S模式切换到U模式。
Linux内核支持多种架构,代码相对晦涩,riscv特权模式切换代码位于arch/riscv/kernel/entry.S,这里不再粘贴,(和M切S模式类似 ,感兴趣自行学习)

2.3.2 中断、异常、系统调用情况特权模式切换

前面2.2介绍了linux用户态(U-mode)如何进入内核态(M-mode),一般来说常见的就是陷入内核态,此时用户态程序在被异常、中断、系统调用等,将暂停当前的任务,切换到高特权级别。

  1. 中断/异常:
    中断本省就可以看做异常的一种,在触发后行为也比较类似,特权模式切换是被动的,这里放到一起。

切换流程:

  • 硬件行为:

    • 暂停当前指令,保存当前PC 到mepc/sepc(中断触发导致会禁用中断(mstatus.MIE/sstatus.SIE 清零))
    • 异常原因存入mcause/scause
    • 当前特权级存入mstatus 的MPP/SPP 字段
    • 根据中断或者异常类型跳转到mtvec(M 模式异常入口)或 stvec(S 模式异常入口)
  • 软件行为:

    • 保存寄存器上下文(手动)
    • 读取 mcause/scause 判断中断或者异常类型
    • 执行中断异常服务例程(如处理定时器、响应外设 / 终止进程、修复缺页)
    • 恢复上下文,执行 mret/sret 返回

具体的流程可以参考: ECLIC中断流程及实际应用 —— RISC-V中断机制(二)

  1. 系统调用流程
    系统调用是软件通过执行ecall指令主动请求进入搞特权级别模式,
  • 硬件行为:

    • 同步触发异常,保存 pc 到 mepc/sepc(指向 ecall 的下一条指令)
    • 设置 mcause/scause 为 ECALL_FROM_U_MODE(code:8)
    • 切换特权模式(通常到 S-mode)
    • 跳转到 stvec 指定的系统调用处理程序
  • 软件处理:

    • 保存上下文。
    • 从 a7 读取系统调用号,a0-a6 读取参数
    • 执行内核服务(如文件读写)
    • 将结果写入 a0,恢复上下文
    • 执行 sret 返回用户态

我们以 Linux 系统 sys_open 系统调用为例,我们看一下用户态程序(特权等级 0, U 模式)是怎么陷入到 Linux 内核(特权等级 1, S 模式)中执行系统调用的。

   22482:	eb8d               	bnez	a5,224b4 <__libc_open+0x64>22484:	03800893          	li	a7,5622488:	f9c00513          	li	a0,-1002248c:	8622               	mv	a2,s02248e:	00000073           	ecall

陷入到内核态后,处理器从 STVEC 寄存器加载异常处理程序入口。在 Linux 内核初始化过程中 (arch/riscv/kernel/head.S),就已经通过 CSR 指令设置好了 STVEC 寄存器,指向 handle_exception 函数:

setup_trap_vector:/* Set trap vector to exception handler */la a0, handle_exceptioncsrw CSR_TVEC, a0/** Set sup0 scratch register to 0, indicating to exception vector that* we are presently executing in kernel.*/csrw CSR_SCRATCH, zeroret

handle_exception 最终会跳转到 handle_syscall,然后从 a7 寄存器中拿到系统调用编号,从 sys_call_table 中索引到最终系统调用处理函数 (arch/riscv/kernel/entry.S):

/* Check to make sure we don't jump to a bogus syscall number. */li t0, __NR_syscallsla s0, sys_ni_syscall/** Syscall number held in a7.* If syscall number is above allowed value, redirect to ni_syscall.*/bgeu a7, t0, 1f/* Call syscall */la s0, sys_call_tableslli t0, a7, RISCV_LGPTRadd s0, s0, t0REG_L s0, 0(s0)
1:jalr s0

参考:
riscv-privileged-20240411.pdf
RISC-V特权模式与寄存器
RISC-V特权等级与Linux内核的启动

相关文章:

  • [Java恶补day9] 438.找到字符串中所有字母异位词
  • 202505系分论文《论信息系统开发方法及应用》
  • 决胜2025:企业级BI产品深度评测与选型指南
  • pip国内镜像源配置
  • 数值积分实验
  • el-table配置表头固定而且高度变化
  • Linux下基本指令的介绍
  • 【芯片设计- SoC 常用模块 9.1 -- PLL 介绍】
  • Redis集群大Key问题深度解决方案
  • 深入解析 IP 代理:原理、应用场景与优化策略
  • 操作系统导论 第37章:磁盘驱动器
  • 脑机新手指南(一):BCILAB 脑机接口工具箱新手入门指南
  • 面试高频图论题『墙与门』:Swift BFS 解法全流程拆解
  • STM32学习第一课--工程建立(云端备份与自我复盘)
  • 回归任务损失函数对比曲线
  • git 一台电脑一个git账户,对应多个仓库ssh
  • 将can日志数据提取到excle中
  • 4. Qt对话框(1)
  • C语言Day9:C语言类型转换规则
  • ADVANTEST D3286 Error Detector OPeration Manual 爱德万测试
  • 成都建立网站的公司网站/百度电话查询
  • 网页制作是建网站的第几步/seo营销优化软件
  • 武汉专业的网站建设公司/建一个企业网站多少钱
  • 网站开发源代码/百度导航和百度地图
  • 移动端网站优化/企业内训机构
  • 梁头网站建设/关键词优化推广公司排名