【risc-v专栏03】CSR 速查与特权架构 M/S/U

本篇定位 :RISC-V 专题第 3 篇。CSR(Control and Status Registers)是 M-mode 程序员的"控制面板"------所有配置(中断使能、trap 入口、模式切换、内存保护、性能计数器)都在 CSR 里。你写 bare-metal、配中断、切栈、调死机,本质就是读写 CSR。本篇给你一张完整 CSR 速查表和每个关键 CSR 的位级详解,重点是你 trap/中断要用到的 mstatus/mtvec/mepc/mcause/mtval/mscratch。
CSR 和通用寄存器的区别

通用寄存器 x0-x31 是"数据寄存器",算术运算用。CSR 是"控制寄存器",不参与算术,只控制 CPU 行为。CSR 有独立的地址空间(0x000-0xFFF),用专门的 csr 指令读写,不能直接 add/lw。这点和 ARM 的"特殊寄存器靠 MSR/MRS 访问"类似,但 RISC-V 的 CSR 数量更多、分工更细。


目录

  • [一、CSR 读写指令](#一、CSR 读写指令)
  • [二、M-mode 核心 CSR 全表](#二、M-mode 核心 CSR 全表)
    • [2.1 trap 相关(④专题详讲,这里先认脸)](#2.1 trap 相关(④专题详讲,这里先认脸))
    • [2.2 计时器/计数器](#2.2 计时器/计数器)
    • [2.3 PMP(物理内存保护,⑥专题)](#2.3 PMP(物理内存保护,⑥专题))
    • [2.4 调试(⑩专题)](#2.4 调试(⑩专题))
  • [三、mstatus 详解(最重要,没有之一)](#三、mstatus 详解(最重要,没有之一))
  • [四、mtvec:trap 入口](#四、mtvec:trap 入口)
    • [Direct 模式(常用,你大概率用这)](#Direct 模式(常用,你大概率用这))
    • [Vectored 模式](#Vectored 模式)
  • [五、mepc / mcause / mtval:死机三件套](#五、mepc / mcause / mtval:死机三件套)
  • 六、mscratch:中断栈切换的关键(重点)

一、CSR 读写指令

四个指令族,都是 csr 开头:

复制代码
csrrw  rd, csr, rs1     # rd = csr; csr = rs1            (读旧值到rd,写新值)
csrrs  rd, csr, rs1     # rd = csr; csr = csr | rs1      (置位,rs1=0则只读)
csrrc  rd, csr, rs1     # rd = csr; csr = csr & ~rs1     (清位,rs1=0则只读)
csrrwi rd, csr, imm     # 立即数版 csrrw
csrrsi rd, csr, imm     # 立即数版 csrrs
csrrci rd, csr, imm     # 立即数版 csrrc

伪指令(汇编常用):

复制代码
csrr  rd, csr           # = csrrs rd, csr, x0   只读
csrw  csr, rs1          # = csrrw x0, csr, rs1  只写
csrs  csr, rs1          # = csrrs x0, csr, rs1  置位
csrc  csr, rs1          # = csrrc x0, csr, rs1  清位

csrrs/csrrc 的妙用:原子位操作

csrrs/csrrc 是"读-改-写"原子操作------硬件保证读出旧值、改位、写回这个序列不被打断。所以改 CSR 单个位用 csrs/csrc,不要用 csrr+改+csrw 三步(三步中间可能被 trap 打断,造成丢失更新)。这点对中断使能位(mie)尤其关键。


二、M-mode 核心 CSR 全表

按功能分组:

2.1 trap 相关(④专题详讲,这里先认脸)

CSR 地址 作用 关键位
mstatus 0x300 机器状态 MIE/MPIE/MPP
misa 0x301 CPU 支持的扩展 MXL31:30 + Extensions25:0
mie 0x304 中断使能 MSIE/MTIE/MEIO + 各外部中断
mip 0x344 中断挂起(只读) MSIP/MTIP/MEIP
mtvec 0x305 trap 入口地址 BASE + MODE1:0
mscratch 0x340 暂存寄存器 全 32 位,你随便用
mepc 0x341 trap 时的 PC 死机指令地址
mcause 0x342 trap 原因 Interrupt31 + Code30:0
mtval 0x343 trap 附加信息 出错地址/指令编码
mcause 例外 --- --- 见下表
mtval --- --- ---
mhartid 0xF14 当前核 ID 多核启动用

2.2 计时器/计数器

CSR 作用
mcycle/mcycleh 周期计数器(低32/高32),性能剖析用
minstret/minstreth 退休指令计数器
mtime 实时时钟(CLINT 提供,内存映射不是 CSR)
mtimecmp 定时器比较寄存器(触发 MTI)

2.3 PMP(物理内存保护,⑥专题)

CSR 作用
pmpcfg0-15 16 个 PMP 区的配置(权限/模式)
pmpaddr0-15 16 个 PMP 区的地址

2.4 调试(⑩专题)

CSR 作用
dcsr 调试控制和状态
dpc 调试 trap 的 PC
dscratch0/1 调试暂存

三、mstatus 详解(最重要,没有之一)

mstatus 是机器状态的"总开关",trap/中断/模式切换全靠它。位布局(rv32):

复制代码
mstatus[31:12]: SD/TSR/TW/TVM/MXR/SUM/MPRV/XS/FS/VS 等
mstatus[12:11]: MPP     ← trap 前的特权模式(M/S/U)
mstatus[7]:     MPIE    ← trap 前的 MIE
mindex[6]:      SPIE
mstatus[5]:     UPIE
mstatus[3]:     MIE     ← M 级全局中断使能
mstatus[1]:     SIE
mstatus[0]:     UIE

关键位详解

MIE(Machine Interrupt Enable,bit 3):M 级全局中断总开关。

  • MIE=1,M 级中断可触发
  • MIE=0,M 级中断被屏蔽
  • trap 进 M 级时,硬件自动 MIE→0(防中断嵌套),trap 前的值存 MPIE

MPIE(bit 7):trap 前的 MIE 值。

  • trap 发生:MPIE ← MIE,MIE ← 0
  • mret 返回:MIE ← MPIE,MPIE ← 1
  • 这个机制保证 mret 后中断状态恢复到 trap 前

MPP12:11:trap 前的特权模式。

  • 00=U, 01=S, 11=M
  • trap 发生:MPP ← 当前模式
  • mret 返回:模式 ← MPP
  • 这是"判断死机时在哪个模式"的关键------读 mstatus.MPP 就知道

MPP 的两个特殊用法:

  • 软件想强制切到 S 模式:写 mstatus.MPP=01,然后 mret
  • 初始化第一个从 M 切 S 的跳转:同样设 MPP=01 再 mret

其他位(选读)

  • FS14:13:浮点单元状态(Off/Initial/Clean/Dirty)。关 FPU 省电、或裸机不用浮点时设 Off
  • VS10:9:向量单元状态(同 FS)
  • MPRV(bit 17):置 1 时,M 模式的 load/store 用 MPP 指定的模式权限(内核访问用户内存用)
  • TVM(bit 20):置 1 禁止 S 模式改页表(S mode 虚拟化用)

四、mtvec:trap 入口

复制代码
mtvec[31:2]: BASE     trap 入口地址(4字节对齐)
mtvec[1:0]:  MODE     模式
  00 = Direct     所有 trap 跳到 BASE
  01 = Vectored   异常跳 BASE,中断跳 BASE + 4*cause
  1x = 保留

Direct 模式(常用,你大概率用这)

所有 trap(异常+中断)都跳到同一个 BASE 地址。你的 trap handler 第一件事是读 mcause 判断是异常还是哪种中断,再分支处理。简单、灵活,MCU bare-metal 标准做法。

Vectored 模式

中断按 cause 走不同入口(BASE + 4*cause),异常仍进 BASE。省一次 mcause 读取,但入口表占用代码空间,且各中断入口都要写保存上下文的汇编。中断多且对延迟敏感时用。

怎么看你用的是哪种

启动汇编里 la t0, trap_vector; csrw mtvec, t0 后,trap_vector 是不是数组?vectored 模式需要一段连续的入口表。大多数 MCU SDK(包括 FreeRTOS 移植)用 direct 模式 + 一个统一 handler 分发,简单可靠。


五、mepc / mcause / mtval:死机三件套

死机现场,这三个 CSR 是你的"黑匣子"。

mepc(0x341)

trap 发生时的 PC------就是出问题那条指令的地址(对于中断,是下一条要执行的指令)。

  • 异常:返回后会重新执行那条指令(若已修复),或跳过
  • 中断:返回后执行下一条
  • 死机调试第一步:打 mepc,addr2line 看是哪行代码

mcause(0x342)

复制代码
mcause[31]:    Interrupt   1=中断, 0=异常
mcause[30:0]:  Code        原因编号

异常 Code(Interrupt=0):

Code 含义 你要做什么
0 指令地址对齐错 查函数指针/jalr 目标对齐
1 指令访问 fault PC 指向不可执行区
2 非法指令 反汇编 mepc 处,看是不是跑飞到数据区
3 Breakpoint ebreak 或调试器
4 Load 地址对齐错 lw/sw 地址没对齐
5 Load 访问 fault 最常见,野指针/未初始化指针
6 Store 地址对齐 ---
7 Store 访问 fault 写只读区/野指针
8 U 模式 ecall 用户态系统调用
9 S 模式 ecall ---
11 M 模式 ecall ---
12-15 栈/页表相关 ---

中断 Code(Interrupt=1):

Code 含义
3 MSI(机器软件中断,IPI)
7 MTI(机器定时器中断,FreeRTOS tick)
11 MEI(机器外部中断,经 PLIC,I2C/UART 等)

死机排查标准流程

  1. 读 mepc → addr2line 定位代码行
  2. 读 mcause → 知道是哪种异常/中断
  3. 读 mtval → 看出错地址
  4. 读 mstatus.MPP → 知道死机时在 M/S/U 哪个模式
  5. 组合判断:如 mcause=5(load fault)+ mtval=0xDEADBEEF + mepc=某读指针行 → 野指针读 0xDEADBEEF
    这五步是你调任何 RISC-V 死机的元动作,刻进肌肉记忆。

mtval(0x343)

trap 附加信息,内容随 mcause 变:

  • 访问 fault(5/7):出错的内存地址
  • 非法指令(2):那条指令的编码
  • 页表 fault:出错地址
  • 其他:可能为 0(实现定义)

mtval 让你能直接看到"哪个地址炸了",不用反推。你栈回溯笔记里没强调这个,但它和 mepc/mcause 一样重要,是死机第一现场。


六、mscratch:中断栈切换的关键(重点)

mscratch(0x340)是一个纯暂存寄存器 ,硬件不给它任何特殊语义,你随便用。但它有一个约定俗成的关键用途:中断栈切换。

为什么需要切栈

RTOS 里,任务跑在用户栈(PSP/线程栈),中断要跑内核栈(MSP/中断栈),避免中断污染任务栈、防止栈溢出时任务和中环互相破坏。ARM Cortex-M 硬件自动切 MSP/PSP(靠 EXC_RETURN bit 2);RISC-V 不自动切,你得用 mscratch 软件切。

切栈标准手法

asm 复制代码
# 初始化时:M-mode sp 存内核栈,mscratch 存任务栈
# (或反过来,看约定)

trap_vector:
    # 进入时:sp 是任务栈,mscratch 是内核栈
    csrrw  sp, mscratch, sp     # 交换 sp 和 mscratch
                                 # 现在 sp = 内核栈, mscratch = 任务栈
    addi   sp, sp, -CONTEXT_SIZE
    sw     ra,  0(sp)
    sw     t0,  4(sp)
    # ... 保存其他寄存器到内核栈 ...

    # 处理中断(C 调用)
    call   trap_handler_c

    # 恢复
    lw     ra,  0(sp)
    # ...
    addi   sp, sp, CONTEXT_SIZE
    csrrw  sp, mscratch, sp     # 换回:sp=任务栈, mscratch=内核栈
    mret

关键点:

  1. csrrw sp, mscratch, sp 原子交换两个 sp------sp 变成内核栈,mscratch 暂存任务栈
  2. 中断处理全程在内核栈上
  3. 退出前再交换回来,sp 恢复任务栈

RISC-V 把切栈交给软件,灵活但每个细节你要负责。ARM 上这些硬件管,你写 handler 像写普通函数;RISC-V 上你必须在 trap entry/exit 显式处理。这是 ④trap 专题的核心难点,也是 RISC-V bare-metal 比 ARM 难的地方。

七、mie / mip:中断使能与挂起

复制代码
mie(0x304,读写):
  bit 3:  MSIE   软件中断使能
  bit 7:  MTIE   定时器中断使能
  bit 11: MEIE   外部中断使能
  bit 8-12: 各特定外部中断(看实现)

mip(0x344,只读):
  bit 3:  MSIP   软件中断挂起
  bit 7:  MTIP   定时器中断挂起
  bit 11: MEIP   外部中断挂起
  • mie 是开关,mip 是状态。某中断要触发,需要 mie 对应位=1 且 mip 对应位=1 且 mstatus.MIE=1。
  • mip 大部分位只读(由硬件/CLINT/PLIC 设),MSIP 可写(用于 IPI:一个核写另一个核的 MSIP 位触发软件中断)。
  • 关全局中断:csrci mstatus, 0x8(清 MIE 位)。开:csrsi mstatus, 0x8。

嵌入式视角:这就是 FreeRTOS portDISABLE_INTERRUPTS 的实现

FreeRTOS RISC-V port 里 portDISABLE_INTERRUPTS() 就是清 mstatus.MIE,portENABLE_INTERRUPTS() 就是置 mstatus.MIE。你调的 ENTER_CRITICAL/EXIT_CRITICAL,底层就是这两条 csr 指令。临界区=临时关 MIE,保证不被中断打断。


八、S-mode / U-mode CSR(选读,知道边界)

S 级有自己一套 CSR(sstatus/stvec/sepc/scause/stval/sscratch/sie/sip/satp),结构对应 M 级但权限受限。U 级几乎没有 CSR(只有几个计时器读取)。

M 级 CSR S 级对应 用途
mstatus sstatus 状态(S 级版)
mtvec stvec S 级 trap 入口
mepc sepc ---
mcause scause ---
mscratch sscratch S 栈切换
mie/sip sie/sip S 级中断
(无) satp 页表基址(S 级 MMU 关键)

satp 是 S 级独有,M 级没有(因为 M 不用页表)。跑 Linux 时 M 级 bootloader 切到 S 级前要配好 satp。你做 MCU bare-metal 不碰 satp,但 AEGIX 跑 Linux 要碰。

M 级能访问 S 级 CSR 吗

能。M 级全权,能读写所有级别的 CSR(M/S/U)。反过来 S 级只能读 S 和部分 U,不能读 M。这是特权架构的核心:高级别能管低级别,低级别受限。


九、CSR 速查总表(打印贴墙)

CSR 地址 一句话
mstatus 0x300 状态总开关(MIE/MPIE/MPP)
misa 0x301 CPU 支持什么扩展
mie 0x304 中断使能(MSIE/MTIE/MEIE)
mtvec 0x305 trap 入口(BASE+MODE)
mscratch 0x340 暂存(切栈用)
mepc 0x341 trap PC(死机指令地址)
mcause 0x342 trap 原因(Int+Code)
mtval 0x343 trap 附加(出错地址)
mip 0x344 中断挂起(只读)
mhartid 0xF14 核 ID
mcycle 0xB00 周期计数器(性能剖析)
pmpcfg0 0x3A0 PMP 配置 0
pmpaddr0 0x3B0 PMP 地址 0
satp 0x180 S 级页表基址

十、本篇小结

  • CSR 是 M-mode 控制面板,用 csrrw/csrrs/csrrc 读写,改位用 csrrs/csrrc 原子操作
  • mstatus 是核心:MIE(中断总开关)/MPIE(trap 前的 MIE)/MPP(trap 前的模式)
  • mtvec 配 trap 入口,direct(统一入口)或 vectored(按 cause 分散)
  • 死机三件套:mepc(地址)+mcause(原因)+mtval(附加),配 mstatus.MPP(模式)→ 排查五步法
  • mscratch 暂存寄存器,核心用途是中断栈切换(csrrw sp,mscratch,sp)
  • mie/mip 是中断使能/挂起,关中断清 mstatus.MIE
  • M 级能访问所有 CSR(M/S/U),S 级只能访问 S 和部分 U
  • S 级独有 satp(页表),跑 Linux 才用

速查表

想干啥 CSR/指令
关全局中断 csrci mstatus, 8(清 MIE bit3)
开全局中断 csrsi mstatus, 8
配 trap 入口 csrw mtvec, &trap_vector
读死机地址 csrr t0, mepc
读死机原因 csrr t0, mcause
读出错地址 csrr t0, mtval
读 trap 前模式 csrr t0, mstatus; (>>11)&3
切中断栈 csrrw sp, mscratch, sp
置某中断使能 csrs mie, (1<<bit)
看中断挂起 csrr t0, mip
周期计数 csrr t0, mcycle
核 ID csrr t0, mhartid

💡技术之路漫漫,分享是为了更好地交流。如果本文的内容对你有启发,希望能得到你的 点赞 👍 和 收藏 ⭐。

如果你在调试过程中遇到了其他问题,欢迎在 评论区 💬 留言,我们一起探讨。也欢迎 关注 👀 我,一起交流底层开发的那些事儿。


相关推荐
单片机设计·······2 小时前
【dz-1162】汽车前玻璃雨刮器设计
c++·单片机·嵌入式硬件
一木 之林2 小时前
多模态大模型一统精讲 NLP 和 CV:从 ViT、CLIP 到 BLIP、LLaVA 的三段式统一架构
人工智能·自然语言处理·架构
157092511342 小时前
Android 架构模式详解:从 MVC、MVP 到 MVVM
android·架构·mvc
兴通物联科技3 小时前
对俄出口诚实标识 2026 时间节点与产线赋码采集技术方案
大数据·服务器·单片机·嵌入式硬件·深度学习·计算机视觉
IvorySQL3 小时前
去 IOE 的最后一公里:IvorySQL 5.4 × RISC-V 实测
数据库·人工智能·ai·postgresql·risc-v
AOI小白新手上路3 小时前
韦东山《ARM 架构与编程》3-3 IMX6ULL 的 GPIO 操作方法 · 学习笔记
arm开发·学习·架构
2601_962219013 小时前
操作日志全留存架构:万象生鲜系统生鲜业务合规审计底层实现方案
微服务·云原生·架构
张彦峰ZYF3 小时前
查询接口调优:无缓存、强实时约束下的系统化性能工程
架构·业务缓存与数据库缓冲机制·强一致、读己之写·读己之写和有界陈旧语义·端到端延迟分段·一致性路由
海宇数据3 小时前
零信任架构实战:基于海宇车型识别精准构建自动化违章处理网关
运维·人工智能·架构·自动化