3. RISC-V 特权架构基础:机器模式、监督模式、用户模式与 CSR 寄存器

RISC-V 特权架构定义了三种核心特权模式(Privilege Modes),以及一套用于配置和控制处理器行为的控制状态寄存器(CSR,Control and Status Register)。理解这些模式与寄存器是进行 xv6 内核移植和板级支持包(BSP)构建的基石。xv6 主要运行在监督模式(S-Mode)下,但启动和底层硬件管理必须依赖机器模式(M-Mode)。

3.1 三种特权模式与层级关系

RISC-V 规范定义了三种特权级别,形成一个严格的层级保护模型。数字越大,特权越高。

模式名称 编码 缩写 主要用途 xv6 中的角色
机器模式 11 M-Mode 硬件复位、异常处理、物理内存属性、安全启动 引导加载程序(如 OpenSBI)运行于此,为 xv6 准备环境
监督模式 01 S-Mode 操作系统内核运行、虚拟内存管理、中断处理 xv6 内核主体运行于此
用户模式 00 U-Mode 应用程序运行、受限指令集、页级保护 xv6 的用户进程运行于此
关键概念: 在典型的 RISC-V 系统中,M-Mode 运行一个称为机器模式监视器(M-Mode Monitor)或 SBI(Supervisor Binary Interface)的固件。xv6 通过 ecall 指令从 S-Mode 陷入 M-Mode,以请求 SBI 服务(如控制台输出、定时器设置)。这种设计将硬件平台相关的代码隔离在 M-Mode,使 xv6 内核本身具有更好的可移植性。
移植要点: 在将 xv6 移植到新的 RISC-V 开发板时,你需要确保 M-Mode 的 SBI 固件(如 OpenSBI)已经正确移植并支持该板卡。xv6 本身不直接操作 M-Mode 的 CSR,而是通过 SBI 调用间接访问。
RISC-V 特权模式层级与转换关系 机器模式 (M-Mode) — 最高权限 复位入口、SBI 固件、物理内存保护 监督模式 (S-Mode) — 操作系统内核 xv6 内核、页表管理、中断处理 用户模式 (U-Mode) — 应用程序 受限指令、用户进程、页级保护 mret ecall / 异常 sret ecall / 异常 说明:箭头表示模式切换的典型路径。M-Mode 通过 mret 进入 S-Mode;S-Mode 通过 sret 进入 U-Mode。异常和 ecall 指令会使模式向更高特权级陷入。

3.2 核心 CSR 寄存器分类与功能

CSR 寄存器是 RISC-V 架构中用于配置和控制处理器状态的关键接口。根据特权模式,CSR 分为三类:机器级(M-Mode)、监督级(S-Mode)和用户级(U-Mode)。xv6 内核主要操作监督级 CSR,但启动阶段会通过 SBI 间接访问机器级 CSR。

3.2.1 机器模式 CSR(M-Mode CSR)

这些寄存器只能在 M-Mode 下通过 csrrw、csrrs 等指令访问。在 S-Mode 或 U-Mode 下访问将触发非法指令异常。

CSR 名称 地址 描述 在 xv6 移植中的角色
mvendorid 0xF11 制造商 ID 用于识别 CPU 厂商,调试时使用
marchid 0xF12 架构 ID 标识处理器实现的 RISC-V 架构版本
mimpid 0xF13 实现 ID 处理器微架构版本信息
mhartid 0xF14 硬件线程 ID 多核系统中每个核心的唯一标识
mstatus 0x300 机器状态寄存器 控制全局中断使能、特权模式切换等
medeleg 0x302 机器异常委托寄存器 将特定异常委托给 S-Mode 处理,xv6 依赖此机制
mideleg 0x303 机器中断委托寄存器 将特定中断委托给 S-Mode 处理
mie 0x304 机器中断使能寄存器 控制 M-Mode 下各中断源的使能
mtvec 0x305 机器陷阱向量基址寄存器 设置 M-Mode 异常处理程序的入口地址
mepc 0x341 机器异常程序计数器 保存发生异常时的返回地址
mcause 0x342 机器异常原因寄存器 指示异常或中断的类型
mtval 0x343 机器陷阱值寄存器 提供异常的附加信息(如访存地址)

3.2.2 监督模式 CSR(S-Mode CSR)

这些寄存器是 xv6 内核直接操作的核心。S-Mode 下的代码可以直接读写它们,用于管理页表、处理中断和异常。

CSR 名称 地址 描述 在 xv6 中的典型用途
sstatus 0x100 监督状态寄存器 控制 S-Mode 中断使能(SIE 位)、用户模式访问权限(SUM 位)
sie 0x104 监督中断使能寄存器 使能/禁用外部中断(SEIE)、定时器中断(STIE)、软件中断(SSIE)
stvec 0x105 监督陷阱向量基址寄存器 设置 xv6 内核的异常/中断处理入口(trampoline 或 kernelvec)
scounteren 0x106 监督计数器使能寄存器 控制 U-Mode 下对性能计数器的访问权限
sscratch 0x140 监督临时寄存器 在陷阱处理中保存 CPU 核心的 proc 结构体指针
sepc 0x141 监督异常程序计数器 保存发生异常时的用户态或内核态返回地址
scause 0x142 监督异常原因寄存器 指示异常类型(如系统调用、页错误、外部中断)
stval 0x143 监督陷阱值寄存器 提供异常的附加信息(如导致页错误的虚拟地址)
satp 0x180 监督地址转换和保护寄存器 控制页表的基地址、地址空间标识符(ASID)和页表模式(Sv39)
常见陷阱: 在 xv6 的上下文切换代码中,sscratch 寄存器被用于在用户态和内核态之间快速交换 proc 指针。如果在移植过程中修改了陷阱处理流程,务必确保 sscratch 的初始化和使用逻辑与 xv6 原版一致,否则会导致内核崩溃。
xv6 内核中关键 CSR 数据流 用户进程 (U-Mode) 执行 ecall 或触发异常 陷阱处理入口 stvec → trampoline 内核调度器 上下文切换 sepc, scause, stval sscratch 页表管理 satp → 页表基址 中断控制器 (PLIC) sie, sstatus.SIE 定时器 (CLINT) sie.STIE, SBI 调用 说明:当用户进程执行 ecall 时,硬件自动将 sepc 设置为返回地址,scause 设置为 8(系统调用), stval 设置为 ecall 指令地址。stvec 指向的 trampoline 代码使用 sscratch 获取当前进程的 trapframe 指针。

3.3 异常委托机制:从 M-Mode 到 S-Mode

默认情况下,所有异常和中断都会陷入 M-Mode。为了让 xv6 内核能够直接处理系统调用、页错误和外部中断,RISC-V 提供了异常委托机制。通过配置 medeleg 和 mideleg 寄存器,可以将特定类型的异常和中断直接委托给 S-Mode 处理,而无需经过 M-Mode 的 SBI 固件。

3.3.1 委托的异常类型

xv6 依赖以下异常被委托给 S-Mode:

  • 系统调用 (Exception Code 8):用户程序通过 ecall 指令请求内核服务。
  • 断点 (Exception Code 3):用于调试器支持。
  • 指令页错误 (Exception Code 12):取指令时发生页错误。
  • 加载页错误 (Exception Code 13):加载指令时发生页错误。
  • 存储/AMO 页错误 (Exception Code 15):存储或原子操作时发生页错误。

3.3.2 委托的中断类型

xv6 依赖以下中断被委托给 S-Mode:

  • 监督软件中断 (SSI, Interrupt Code 1):用于多核处理器间的核间中断(IPI)。
  • 监督定时器中断 (STI, Interrupt Code 5):用于实现时间片轮转调度。
  • 监督外部中断 (SEI, Interrupt Code 9):来自 PLIC(平台级中断控制器)的设备中断。
移植检查清单: 在移植 xv6 时,你需要确认 M-Mode 的 SBI 固件(如 OpenSBI)是否正确配置了 medeleg 和 mideleg。如果委托配置错误,xv6 将无法收到任何中断或系统调用,导致系统无响应。你可以通过检查 SBI 的启动日志或使用调试器读取这两个 CSR 的值来验证。

3.4 陷阱处理流程:从用户态到内核态

当用户进程执行 ecall 指令或发生异常时,硬件自动执行以下步骤:

  1. 保存返回地址:将当前 PC 值保存到 sepc 寄存器。
  2. 记录异常原因:将异常类型编码写入 scause 寄存器。
  3. 记录附加信息:将异常相关地址(如页错误地址)写入 stval 寄存器。
  4. 关闭中断:将 sstatus.SIE 位复制到 sstatus.SPIE,然后清除 sstatus.SIE 以禁止嵌套中断。
  5. 切换模式:将当前特权模式(U-Mode)保存到 sstatus.SPP,然后切换到 S-Mode。
  6. 跳转到处理程序:将 PC 设置为 stvec 寄存器中的值(在 xv6 中为 trampoline 页的起始地址)。

以下代码展示了 xv6 中 trampoline.S 中处理用户态陷阱的入口部分:

# 来自 xv6 源码 kernel/trampoline.S
# 用户态陷阱处理入口
.globl uservec
uservec:
    # 1. 交换 sscratch 和 a0,sscratch 中保存了 trapframe 指针
    csrrw a0, sscratch, a0

    # 2. 保存用户寄存器到 trapframe
    sd ra, 40(a0)
    sd sp, 48(a0)
    # ... 保存其他寄存器 ...

    # 3. 保存用户 PC(sepc)到 trapframe
    csrr t0, sepc
    sd t0, 112(a0)

    # 4. 保存用户模式(sstatus)到 trapframe
    csrr t1, sstatus
    sd t1, 120(a0)

    # 5. 加载内核栈指针
    ld sp, 8(a0)

    # 6. 加载内核页表(satp)
    ld t0, 16(a0)
    csrw satp, t0
    sfence.vma zero, zero

    # 7. 跳转到 C 语言处理函数 usertrap
    ld t0, 0(a0)
    jr t0
性能优化提示: 在 trampoline 代码中,使用 csrrw 指令原子地交换 sscratch 和 a0 是一种常见的优化技巧。它避免了在进入陷阱处理时先保存 a0 到内存的额外开销。在移植到新的 CPU 时,请确保 sscratch 在进程创建时已被正确初始化为该进程的 trapframe 地址。

3.5 本章小结

RISC-V 的特权架构为 xv6 提供了清晰的保护边界和灵活的配置能力。M-Mode 作为可信计算基(TCB),运行 SBI 固件,为 S-Mode 的 xv6 内核提供平台抽象。S-Mode 的 CSR 寄存器是 xv6 实现进程管理、内存虚拟化和中断处理的核心工具。理解这些模式与寄存器的交互关系,是成功将 xv6 移植到新硬件平台的第一步。