ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RISC-V CSR速查与特权模式详解:M/S/U模式、中断与寄存器操作

RISC-V CSR速查与特权模式详解:M/S/U模式、中断与寄存器操作 如果你做过一段时间的 RISC-V 裸机或者内核开发大概率会被 CSR 这个东西搞得又爱又恨。CSR 的全称是 Control and Status Register翻译过来就是控制与状态寄存器它不占通用寄存器组每条 CSR 都对应一个 12 位地址用来存放机器状态、中断控制、异常原因、地址翻译配置这类“杂七杂八但每个都要命”的信息。而在 CSR 之上M/S/U 三个字母代表了 RISC-V 特权架构里最核心的三个运行等级Machine、Supervisor、User。这篇是【risc-v专栏】的第三篇主题是 CSR 速查与特权架构。我最初接触这套东西的时候最崩溃的不是记不住寄存器名字而是搞不清楚什么时候该写 M 态寄存器什么时候该写 S 态寄存器以及为什么一个 breakpoint 异常会从 U 态一路跳到 M 态。这篇内容就是把我在实际开发中反复用到的那部分掏出来整理成一张真正能查得动、查完能上手的速查体系。1. 特权模式不是权限等级而是三套几乎独立的运行环境1.1 三个模式的“出场分工”很多人第一次看 M/S/U 三个模式容易套用 x86 的 Ring0/Ring3 那套“权限高低”的直觉。但在 RISC-V 里更准确的理解是这三个模式是三套几乎独立的运行环境每一套都有自己的 CSR 集合、自己的异常入口、自己的返回指令和相对独立的状态视图。M 模式Machine Mode机器态。芯片上电复位后默认进入 M 模式它拥有最高权限可以读写所有 CSR、配置 PMP 物理内存保护、操作所有外设。BootROM、OpenSBI、Coreboot、裸机 RTOS 通常都跑在 M 模式。S 模式Supervisor Mode监管态。对应现代操作系统内核所在的层级能访问页表、配置 satp、处理来自 U 模式系统调用是 Linux 内核在 RISC-V 上的主场。U 模式User Mode用户态。应用程序只能待在这里访存和指令执行受 M/S 模式设置的规则约束想干点“出格”的事只能通过 ecall 或异常进入更高模式由内核处理。这三个模式之间的关系我用一个不太严谨但很好记的类比M 模式像房东拿的是总钥匙S 模式像物业公司管理楼里的日常运营U 模式像租客只能在自己房间里折腾。1.2 从复位到用户态一条典型路径一个跑 Linux 的系统特权模式的切换路径通常是这样的上电硬件复位跳转到 M 模式固件入口M 模式固件比如 OpenSBI完成 DDR、时钟、串口、中断控制器等基础初始化OpenSBI 通过 mret 切到 S 模式跳转 Linux 内核入口Linux 内核在 S 模式配置 satp、异常向量 stvec建立进程地址空间内核通过 sret 回到 U 模式开始运行用户进程用户进程执行系统调用 ecall产生环境调用异常CPU 陷入 S 模式交给内核内核处理完再次 sret 返回 U 模式。在这条链路里M 模式并不参与每一次系统调用这就是后面要说的“委托”机制的功劳。如果每次 ecall 都先落到 M 模式再让 M 模式软件呼哧带喘地跳回 S 模式性能上是灾难。1.3 不是所有 RISC-V 芯片都三件套齐活这里要特别提醒一点M/S/U 三个模式不是 RISC-V 的强制配置。规范允许只实现 M 模式这种情况下芯片就是一颗简单的裸机控制核没有虚拟内存也没有用户态隔离。常见的情况有只实现 M很多 MCU 级 RISC-V 核比如 SiFive 的 E31/E76、蜂鸟 E203 这类面向 IoT 场景的核通常就只有一个 M 模式实现 MU可能支持 User 模式但没做 S 模式下的内存管理完整 MSU跑 Linux 的目标基本都是这个组合。所以在看一颗芯片的 CSR 手册之前第一步是确认它实现了哪几个模式。如果没有 S 模式那所有 S 态 CSR 地址访问都会触发非法指令异常调试时很容易被误判成“CPU 有问题”。2. 12位CSR地址本身就是一张速查表2.1 地址区间速记法CSR 地址是 12 位范围 0x000 到 0xFFF总共 4096 个编号。虽然大部分地址是保留项但这 4096 个编号在布局上是有规律的。很多教程喜欢直接扔一张“CSR 地址编码规则”的表格但说实话作为一个靠“背规律”过日子的人我更喜欢按地址段来记。下面这张表是我干活时经常对照的地址段段类型典型 CSR谁能访问0x000 - 0x0FF用户态 CSRfflags 0x001、frm 0x002、fcsr 0x003U 模式及以上0x100 - 0x1FF监管态 CSRsstatus 0x100、stvec 0x105、sepc 0x141、satp 0x180S 模式及以上0x200 - 0x2FFHypervisor 扩展预留vsstatus 0x200、hedeleg 0x602 等H 扩展启用时0x300 - 0x3FF机器态 CSRmstatus 0x300、mtvec 0x305、mepc 0x341、pmpcfg0 0x3A0M 模式0xB00 - 0xBFF机器态计数器mcycle 0xB00、minstret 0xB02M 模式0xC00 - 0xCFF用户态计数器cycle 0xC00、time 0xC01、instret 0xC02U 模式及以上0xF00 - 0xFFF机器态只读信息mvendorid 0xF11、marchid 0xF12、mhartid 0xF14M 模式只读这个分段有什么用看到 0x305 就知道是机器态 trap 向量看到 0x141 就知道是监管态异常 PC看到 0xC00 就知道这是用户态能读的 cycle 计数器。后面的速查表里我列出的每一个地址都有对应的段位查起来心里会非常有底。2.2 只读与只写的识别门道除了按模式分段CSR 地址还有一个很实用的观察角度0xF00 - 0xFFF 段的 CSR 基本都是只读的。mvendorid厂商 ID、marchid架构 ID、mimpid实现 ID、mhartid硬件线程 ID这几个是机器态只读信息的代表。另外0x7B0 - 0x7FF 段是 Debug 模块使用的 CSR比如 dcsr 0x7B0、dpc 0x7B1、dscratch0 0x7B2 这些虽然可读可写但一般只有调试器或者 M 模式调试固件会碰它普通应用开发完全不涉及。这也是为什么很多人的速查表里永远看不到 0x7 段的原因。2.3 用 misa 一分钟确认 CPU 能力清单misaMachine ISA这个 CSR 地址是 0x301主要负责告诉你当前 CPU 支持哪些指令集扩展。misa 的每一位对应一个英文字母例如bit 0A原子操作扩展bit 2C压缩指令扩展bit 3D双精度浮点扩展bit 5F单精度浮点扩展bit 8I整数基础指令集bit 12M整数乘除法扩展bit 18SS 模式bit 20UU 模式bit 21V向量扩展。在 M 模式下执行csrr t0, misa就能读到这些位。我排查“为什么这颗核不支持某条指令”时第一件事就是先把 misa 读出来看目标扩展位有没有置 1。如果连 S/U 位都没置 1那后面的虚拟内存相关调试可以直接跳过因为没有硬件支持。3. 常用 CSR 速查表按使用场景分组3.1 机器态核心状态寄存器这是我在裸机开发中打交道最多的几个 CSR也是理解其它 CSR 的基础。这里直接列一张核心表地址名称访问说明0x300mstatusRW机器态状态包含全局中断开关、异常发生前特权级等0x301misaRW指令集扩展信息0x304mieRW机器态中断使能每一位对应一个中断类型0x344mipRW机器态中断等待标志软件可置位/清除软件中断0x320mcountinhibitRW计数器启停控制置 1 对应计数器停止计数mstatus 里最常用的字段集中在低十几位字段位段作用MIEbit 3M 模式全局中断使能0 表示关闭所有 M 模式中断MPIEbit 7进入 trap 之前 MIE 的旧值mret 时用它恢复MPPbit 12:11进入 trap 之前的特权级mret 返回时用它决定切到哪个模式SIEbit 1S 模式全局中断使能SUMbit 18允许 S 模式访问 U 模式页面MPRVbit 17内存访问保护位置 1 后 M 模式访存按 MPP 指定的特权级检查有一个非常常见的坑很多人只设了 mie 里的某个中断使能位却没把 mstatus.MIE 置 1以为中断就能进来。结果是中断一直 pending但 CPU 完全不响应。全局开关和局部开关是“与”的关系任何一个没打开都不行。3.2 trap 处理相关寄存器处理异常和中断是 CSR 的另一个主战场。以下的“四件套”是每次 trap 都要用到的地址名称访问说明0x305mtvecRWM 模式 trap 入口地址低 2 位是模式选择0x341mepcRW异常发生时被中断/异常指令的 PC0x342mcauseRW异常原因最高位区分中断与异常低 31/63 位是原因编号0x343mtvalRW异常相关附加信息比如非法指令编码、访存地址、页表出错地址mtvec 的布局很简单基地址按 4 字节对齐放在高位最低 2 位是模式。MODE0 表示所有 trap 都跳转到同一个基地址MODE1 表示向量模式中断会根据中断号偏移跳转。向量模式的偏移公式是BASE 4 × cause。mcause 的格式要单独记一下最高位为 1 表示这是中断为 0 表示这是异常。RV64 下最高位是 bit 63RV32 下是 bit 31。异常原因编号和中断编号不是一套编号。比如异常码异常类型0指令地址未对齐1指令取指访问错误2非法指令3断点4加载地址未对齐5加载访问错误6存储地址未对齐7存储访问错误8来自 U 模式的 ecall9来自 S 模式的 ecall11来自 M 模式的 ecall12指令页错误13加载页错误15存储页错误中断类 mcause 的编号则对应1 是 S 模式软件中断3 是 M 模式软件中断5 是 S 模式定时器中断7 是 M 模式定时器中断9 是 S 模式外部中断11 是 M 模式外部中断。3.3 中断委托与控制类 CSR真正让 RISC-V 的多模式协作变得灵活的是委托机制相关的两个寄存器medeleg 和 mideleg。地址名称访问说明0x302medelegRW机器态异常委托每一位对应一个异常码0x303midelegRW机器态中断委托每一位对应一个中断号0x306mcounterenRW机器态计数器访问使能控制 S 模式能否读 cycle/time/instret0x106scounterenRW监管态计数器访问使能控制 U 模式能否读计数器把 medeleg 的 bit 8 置 1U 模式发出的 ecall 就不会跳到 M 模式而是直接进入 S 模式把 mideleg 的 bit 5 置 1S 模式定时器中断也直接由 S 模式处理。这个设计在跑 Linux 的系统里是必然配置因为整个系统调用和大部分中断都希望由内核自己消化而不是每次都回 M 模式绕一圈。3.4 监管态寄存器别名M 模式有的很多 CSRS 模式都有对应版本只是把 m 换成 s功能类似地址名称说明0x100sstatusS 模式状态是 mstatus 的一个低权限视图0x104sieS 模式中断使能0x105stvecS 模式 trap 入口0x141sepcS 模式异常 PC0x142scauseS 模式异常原因0x143stvalS 模式异常附加信息0x144sipS 模式中断等待0x180satpS 模式页表基地址与 MMU 模式这些寄存器里satp 是开启虚拟内存的关键。它包含 MODE 字段、ASID 字段和 PPN 字段。在 RV64 的 Sv39 模式下MODE 字段是 8PPN 指向根页表的物理页号。启动 Linux 时内核会在 S 模式配置好 satp 才敢真正开启虚存然后在 sret 返回 U 模式时用户进程看到的已经是虚拟地址空间了。3.5 用户态一眼就能认的 CSRU 模式能直接访问的 CSR 不多但有两个场景绕不开浮点状态和计数器。0x001 fflags浮点异常标志0x002 frm浮点舍入模式0x003 fcsr浮点控制状态寄存器是 fflags 和 frm 的打包视图0xC00 cycleCPU 周期计数器0xC01 time墙上时钟计数器0xC02 instret已退休指令计数器U 模式读计数器不是无条件的S 模式要先通过 scounteren 放行M 模式还要通过 mcounteren 放行。如果你在 U 模式读 time 返回 0 或者触发非法指令别急着怀疑硬件先检查这两级使能位。4. CSR 访问指令CSRRW/CSRRS/CSRRC 的原子语义4.1 六条指令一张表RISC-V 给 CSR 访问设计了专门的指令所有 CSR 操作都必须通过这些指令完成。它们不是普通 load/store不能用寄存器间接寻址 CSR 地址CSR 地址要作为立即数编码在指令里。指令语义典型伪指令csrrw rd, csr, rs1原子读旧值到 rd再写入 rs1csrw csr, rs1rd 为 x0csrrs rd, csr, rs1原子读旧值到 rd再用 rs1 置位csrr rd, csrrs1 为 x0csrrc rd, csr, rs1原子读旧值到 rd再用 rs1 清位csrc csr, rs1rd 为 x0csrrwi rd, csr, uimmcsrrw 的立即数版本csrwi csr, uimmcsrrsi rd, csr, uimmcsrrs 的立即数版本csrsi csr, uimmcsrrci rd, csr, uimmcsrrc 的立即数版本csrci csr, uimm注意 csrr 其实是伪指令它会被汇编器翻译成csrrs rd, csr, x0因为 rs1 是 x0所以只读不置位。csrw 则是csrrw x0, csr, rs1把目标寄存器写成 x0表示读出的旧值直接丢弃只做写操作。4.2 读-改-写为什么必须是原子的中断处理代码里经常需要对 CSR 的某几个位单独修改而不影响其它位。比如想开定时器中断得在 mie 里把 MTIE 位置 1但你不能把整个 mie 覆盖成 0否则其它中断源会被误关。如果没有 csrrs 这种读-改-写指令就得手动读出、修改、写回三步。这三步之间可能正好来一个中断导致修改丢失。csrrs/csrrc 把“读旧值按掩码置位/清位写回”合并成一条不可分割的原子操作这正是它们存在的意义。在硬件实现上这通常等价于读端口和写端口同时动作中间天然不会被其它事件插入。4.3 权限不够会怎样CSR 访问是有特权检查的。你在 U 模式去读 0x300 地址的 mstatusCPU 会直接抛出一个非法指令异常而不是温和地返回 0。同理你在 S 模式去写 0x300 的 mstatus也会非法。所以当调试器里出现 “Illegal Instruction” 而对应的指令看起来完全正常时第一反应应该是这条 CSR 的访问权限是否匹配当前特权级我踩过最无语的一次是在 U 模式测试代码里顺手读了一下 mhartid本来想确认核的 ID结果整个进程被 SIGILL 干掉查了半天才发现特权级不对。4.4 C 代码里的正确读写姿势在 C 里操作 CSR通常用内联汇编封装。由于 CSR 编号是立即数编译器要求它是编译期常量不能用一个变量传进来static inline unsigned long csr_read(unsigned long csr_num) { unsigned long val; asm volatile(csrr %0, %1 : r(val) : i(csr_num)); return val; } static inline void csr_write(unsigned long csr_num, unsigned long val) { asm volatile(csrw %0, %1 : : i(csr_num), r(val)); } static inline void csr_set_bits(unsigned long csr_num, unsigned long bits) { asm volatile(csrs %0, %1 : : i(csr_num), r(bits)); } static inline void csr_clear_bits(unsigned long csr_num, unsigned long bits) { asm volatile(csrc %0, %1 : : i(csr_num), r(bits)); }用法示例csr_write(0x305, (unsigned long)trap_vector); // 设置 mtvec csr_set_bits(0x304, 0x80); // 打开 M 模式定时器中断 MTIE csr_set_bits(0x300, 0x8); // 打开全局中断 MIE5. 模式切换的完整链路ecall、中断、mret/sret5.1 一次 trap 里硬件到底干了什么当异常或中断发生时硬件不是只跳个转就完事。以 U 模式触发 ecall 且目标为 M 模式为例硬件自动完成这几件事将当前 PC 存入 mepc将原因编码存入 mcause这里是 8根据 mtvec 跳转到 trap 入口把当前特权级记录到 mstatus.MPP也就是 U 模式把当前 mstatus.MIE 的值挪到 MPIE 保存然后清零 MIE关闭中断把特权级切换到 M 模式。这一整串动作是硬件完成的不需要软件参与。trap 入口的汇编代码要做的事则是保存所有可能被破坏的通用寄存器然后跳到 C 处理函数处理完再恢复寄存器最后执行 mret。mret 的动作基本是上面流程的逆过程PC 切回 mepc、特权级切回 mstatus.MPP、MIE 从 MPIE 恢复并把 MPP 置回 U 模式。sret 同理只不过作用于 sepc 和相关 S 模式状态。5.2 委托机制让 U 模式的 ecall 直接进 S 模式如果没有委托每次 U 模式的系统调用都会落入 M 模式。OpenSBI 这类 M 模式固件就得先把 trap 记下来再跳回 S 模式让内核处理处理完还要跳回 M 模式再 mret 回 U 模式。这种“绕道”没有任何意义还白白增加切换成本。有了 medeleg代码可以把 U 模式 ecall 这个异常直接委托给 S 模式。这样U 模式执行 ecallCPU 检查 medeleg.bit8发现该异常被委托给 S 模式硬件执行的是 S 模式 trap 动作PC 存入 sepc原因存入 scause跳转到 stvec特权级切到 S 模式Linux 内核在 S 模式正常处理系统调用处理完 sret 回 U 模式。整个过程完全不经过 M 模式。类似地mideleg 控制哪些中断可以只在 S 模式处理。跑 Linux 的板子上S 模式定时器中断和外部中断几乎必然被委托。需要注意的是M 模式自身产生的异常和中断比如 M 模式 ecall、M 模式外部中断不能被委托给低特权模式因为委托的目的是把低特权层的事交给上一层直接处理不能把更高特权层的事甩回给低特权层。5.3 向量模式的真相mtvec 的 MODE 字段如果设成 1就启用了向量化 trap。但这里有个容易误解的点向量化并不是所有异常都按 cause 偏移跳转。规范里的规则是同步异常仍然统一跳到基地址只有中断才会根据中断 cause 做偏移跳转。也就是说在向量模式下如果来了一个 M 模式定时器中断cause 是 7CPU 会跳到BASE 4 × 7 BASE 28。如果你在 BASE 处写了通用异常处理函数在 BASE28 处写了定时器中断处理函数那么中断触发时就直接进了定时器处理函数省去在软件里判断 cause 的步骤。但同步异常比如非法指令、缺页仍然全部进 BASE 这个通用入口。这个设计是因为中断需要低延迟分发而同步异常通常还要保留统一处理逻辑。5.4 没有 banked 寄存器上下文切换要自己扛从 ARM 转过来的朋友可能会有一个惯性认知异常来了硬件自动切换一组影子寄存器。RISC-V 明确不做这件事。trap 发生后所有通用寄存器几乎都和进入 trap 前一样软件必须自行把现场保存到栈里。这里就有一个经典的“先有鸡还是先有蛋”问题trap 进来时连 sp 都可能还在被中断的上下文里怎么保证能用栈保存现场答案是用 mscratch 或者 sscratch 这类 scratch 寄存器。常见的套路是在正常执行时把真实栈指针保存在 mscratch 里进入 trap 后先用 csrrw 把 mscratch 和某个临时寄存器互换把真实 sp 换出来用这个 sp 在栈上保存所有通用寄存器处理完成后恢复寄存器再用 csrrw 把 sp 和 mscratch 换回去。这个“scratch 换寄存器”的手法是 RISC-V 裸机的必修课。如果 trap 入口上来就直接用 sp你保存的其实是原来用户程序那个栈一旦用户栈是无效的整个现场都会被写坏。6. 会被名字坑到的“另一个CSR”压缩稀疏行存储6.1 两种 CSR 完全不是一回事写这篇文章之前我在查资料时看到有个热搜词特别有意思“邻接表 和csr压缩存储 内存空间消耗是同一个量级吗”。第一反应是这跟 RISC-V 的 CSR 速查有什么关系后来想明白了因为图算法里也有一个简称 CSR 的术语Compressed Sparse Row压缩稀疏行存储。它和图结构里的邻接表是同一类东西都是用来存稀疏图的。这俩一个叫 Control and Status Register一个叫 Compressed Sparse Row中文里都能简写成 CSR但完全不搭界。看 RISC-V 相关文档时搜“CSR”经常会搜出一堆图算法的博客刚入门的朋友很容易被绕晕。所以这里单独拎出来讲清楚对比项RISC-V CSR图算法 CSR全称Control and Status RegisterCompressed Sparse Row领域指令集架构数据结构/图存储作用控制 CPU 状态、处理异常中断压缩存储稀疏矩阵/图的邻接关系典型载体mstatus、mtvec、satp 等row_ptr、col_idx、val 数组6.2 邻接表和 CSR 压缩存储空间复杂度怎么比回到那个问题本身邻接表和 CSR 压缩存储的内存空间消耗是同一个量级吗结论先说渐进量级一样都是 O(|V| |E|)但常数项有明显差距实际占用不同。邻接表的典型实现是一个长度为 |V| 的头指针数组每条边对应一个链表节点节点里存邻接顶点编号、下一条边指针可能还有权值。无向图里每一条边要在两个端点的链表里各出现一次。CSR 压缩存储则是三个数组row_ptr 长度为 |V|1记录每一行邻接表在 col_idx 里的起止位置col_idx 长度为总边数无向图也是两倍按顶点编号顺序连续存放邻接顶点如果带权再加一个等长的 val 数组。两者空间差距主要来自邻接表的 next 指针。CSR 把邻接关系变成顺序数组省掉了每个节点存 next 指针的开销内存布局也更连续遍历邻接顶点时 cache 命中率明显更好。邻接表的好处是动态增删边方便CSR 则更适合静态图和大规模图。我举个具体数字假设 100 万顶点、200 万条无向边带 32 位权值。邻接表头指针数组约 4MB边节点 400 万个每个节点按 12 字节算约 48MB合计 52MB 上下CSRrow_ptr 约 4MBcol_idx 400 万 × 4 字节约 16MBval 同样 16MB合计 36MB 左右。所以同样是 O(VE) 量级CSR 通常能省掉百分之二三十的空间而且顺序访问性能更稳。这也解释了为什么大规模图计算框架普遍采用 CSR 而不是邻接表。7. 调试与踩坑记录读 CSR 之前先确认自己身在哪里7.1 平台差异比想象中大最后这部分是我在多个 RISC-V 环境里跑代码总结出来的经验值得每一个刚从模拟器上手的人看一看。不同平台对 CSR 的支持范围差异很大。QEMU/Spike 这类模拟器对特权规范支持比较完整很多 CSR 都能访问。但真实芯片就未必了蜂鸟 E203 这类 MCU 级核没有 S 模式你去读 satp 大概率得到非法指令某些 RV32 核的计数器高位 CSR比如 mcycleh可能也存在还有的平台对 misa 的某些扩展位置 0导致依赖某条指令的代码直接崩。所以在写通用代码时不要假设所有 CSR 都存在。访问之前可以用 misa 探测能力。如果是在 Linux 内核里那 S 模式相关的 CSR 基本都有硬件兜底但如果是裸机就得老老实实按芯片手册筛一遍。我建议拿到一块新板子先做一件非常简单的事写一个 M 模式的测试程序把 mstatus、misa、mvendorid、marchid、mimpid、mhartid 这几个 CSR 读出来串口打印。这一步能确认三件事当前代码确实跑在 M 模式、CPU 支持哪些扩展、能否在 M 模式读到机器信息。跑通之后再开始动中断和地址翻译。7.2 中断进不来的排查过程裸机开发里“我开了中断但它就是不响应”这个问题出现频率极高。我整理一个标准的排查链路按顺序检查确认 mtvec 已设置且指向的地址确实是可执行代码。常见错误是 mtvec 写了一个指针变量但地址还没有完成重定位确认 mstatus.MIE 置 1。这是全局总开关漏掉它一切都白搭确认 mie 中对应的中断类型位也置 1。比如用定时器中断就要置 MTIE确认产生中断的外设确实在 pending。读 mip定时器中断位 MTIP 应该为 1如果用了 S 模式委托还要同时检查 mideleg 和 sie/stvec 是否正确如果中断源挂在外部中断控制器上比如 PLIC还要检查 M 模式外部中断使能 MEIE 以及 PLIC 内部的使能和优先级配置。我有一个朋友调试了两天最后发现是第 4 步没做定时器根本没有启动mip 里一直是 0全局中断开了也白开因为它压根没有中断可以响应。7.3 其他高发坑位清单mret 后跳飞检查 mstatus.MPP 是否正确记录。如果 MPP 是 0U 模式但 U 模式代码栈和入口还没准备好mret 就会跳到一个不可执行的地方。更稳妥的做法是在切换模式之前先确认目标模式的环境已经初始化完毕。读计数器读到 0在 U 模式读 cycle/time需要 mcounteren 和 scounteren 两级放行少一级都不行。改完 satp 就死写 satp 等于告诉 CPU “页表结构变了”如果没有先把根页表准备好、ASID 管理好CPU 下一步取指就会触发页错误。调试时最好关闭 MMU 逐步开启。在 U 模式访问 M 模式 CSR直接 SIGILL不是返回值不对的问题。向量模式陷阱设了 mtvec 的低 2 位为 01却只在基地址放了处理函数没有在对应偏移处放中断处理函数导致中断跳到一个空地址。最后再分享一个我个人的体会CSR 这东西光靠背列表是背不下来的但如果你把“地址区段对应特权级”和“trap 流程对应 CSR 分工”这两条主线抓住了大部分常用 CSR 都能自己推出来遇到不确定的再翻规范。这篇速查表是我自己在做中断控制器驱动和启动代码时最常用的“家底清单”下一篇专栏我打算拆解 satp 和虚拟内存地址翻译那篇会更依赖今天这张表的第 3 章建议先把基础内容过一遍。
返回列表