【risc-v专栏 06b】内存架构深挖:PMA / ePMP / Cache / CMO / MMU 细节 / RVWMO 形式化

本篇定位 :\[06-内存 PMP-MMU-RVWMO] 的深度展开。06 是"知道有什么、怎么配",本篇是"为什么、形式化、边界、硬件细节 "。补充PMA、 PMP 增强(ePMP/mseccfg)、Cache 架构与 CMO 扩展、MMU 的 A/D 位/TLB/sfence.vma/大页、RVWMO 三公理与 fence 位详解、原子操作 reservation set 与 aq/rl。

读完你能:调 PMA 相关的诡异 fault、用 ePMP/mseccfg 做安全隔离、配 CMO 做 cache 维护、看懂页表 A/D 位管理策略、说清 RVWMO 三公理、写正确的 LR/SC 自旋锁。


目录

  • 一、内存访问的完整路径(先建全景)
  • [二、PMA(Physical Memory Attributes)](#二、PMA(Physical Memory Attributes))
    • [2.1 PMA 是什么](#2.1 PMA 是什么)
    • [2.2 PMA 检查的属性清单](#2.2 PMA 检查的属性清单)
    • [2.3 Memory type 三类(最核心)](#2.3 Memory type 三类(最核心))
      • [2.3.1 视角一:按"缓存行为"分(cache / no-cache / device)](#2.3.1 视角一:按"缓存行为"分(cache / no-cache / device))
      • [2.3.2 视角二:按"地址映射完整性"分(Main Memory / I/O / Empty)](#2.3.2 视角二:按"地址映射完整性"分(Main Memory / I/O / Empty))
        • [(1) Main Memory(主存)](#(1) Main Memory(主存))
        • [(2) I/O Memory(I/O 内存)](#(2) I/O Memory(I/O 内存))
        • [(3) Empty(空区)](#(3) Empty(空区))
    • [2.4 PMA 触发的 fault](#2.4 PMA 触发的 fault)
    • [2.5 PMA 怎么查 / 怎么知道](#2.5 PMA 怎么查 / 怎么知道)
    • [2.6 PMA vs PMP(易混,必须分清)](#2.6 PMA vs PMP(易混,必须分清))
    • [2.7 PMA 对你 M-mode 工作的实际影响](#2.7 PMA 对你 M-mode 工作的实际影响)
  • [三、PMP 增强:ePMP / mseccfg](#三、PMP 增强:ePMP / mseccfg)
    • [3.1 基础 PMP 的局限(回顾 + 暴露问题)](#3.1 基础 PMP 的局限(回顾 + 暴露问题))
    • [3.2 mseccfg(Machine Security Configuration)CSR](#3.2 mseccfg(Machine Security Configuration)CSR)
    • [3.3 MML:让 PMP 约束 M-mode](#3.3 MML:让 PMP 约束 M-mode)
    • [3.4 MMWP:M-mode 白名单](#3.4 MMWP:M-mode 白名单)
    • [3.5 ePMP 的 E 位(Entry)](#3.5 ePMP 的 E 位(Entry))
    • [3.6 安全场景应用(AEGIX 方向)](#3.6 安全场景应用(AEGIX 方向))
      • [3.7 ePMP 配置示例(安全启动后)](#3.7 ePMP 配置示例(安全启动后))
  • [四、Cache 架构与 CMO 扩展](#四、Cache 架构与 CMO 扩展)
    • [4.1 Cache 层次](#4.1 Cache 层次)
    • [4.2 Cache 问题(06 的概念深化)](#4.2 Cache 问题(06 的概念深化))
    • [4.3 Cache 维护操作(术语要分清)](#4.3 Cache 维护操作(术语要分清))
      • [4.4 CMO 扩展(Zicbom / Zicboz / Zicbop)⭐](#4.4 CMO 扩展(Zicbom / Zicboz / Zicbop)⭐)
    • [4.5 DMA 一致性三种模式(深化 06)](#4.5 DMA 一致性三种模式(深化 06))
    • [4.6 自修改代码与 fence.i](#4.6 自修改代码与 fence.i)
  • [五、MMU 深入(超越 06 的 Sv39 概念)](#五、MMU 深入(超越 06 的 Sv39 概念))
    • [5.1 页表模式全表](#5.1 页表模式全表)
    • [5.2 页表项位段(Sv39,深化 06)](#5.2 页表项位段(Sv39,深化 06))
    • [5.3 A/D 位管理(06 没讲的关键)⭐](#5.3 A/D 位管理(06 没讲的关键)⭐)
      • [(1) 硬件管理](#(1) 硬件管理)
      • [(2) 软件管理](#(2) 软件管理)
    • [5.4 TLB 与 sfence.vma](#5.4 TLB 与 sfence.vma)
    • [5.5 ASID(Address Space ID)](#5.5 ASID(Address Space ID))
    • [5.6 大页(Superpages)](#5.6 大页(Superpages))
    • [5.7 satp 切换与模式切换](#5.7 satp 切换与模式切换)
  • [六、RVWMO 形式化](#六、RVWMO 形式化)
    • [6.1 RVWMO 的三层模型](#6.1 RVWMO 的三层模型)
    • [6.2 PPO(保序规则)](#6.2 PPO(保序规则))
    • [6.3 三公理](#6.3 三公理)
      • [(1) Load Value Axiom(取值公理)](#(1) Load Value Axiom(取值公理))
      • [(2) Atomicity Axiom(原子性公理)](#(2) Atomicity Axiom(原子性公理))
      • [(3) Progress Axiom(前进公理)](#(3) Progress Axiom(前进公理))
      • [6.4 fence 指令位详解(06 跳过的细节)](#6.4 fence 指令位详解(06 跳过的细节))
    • [6.5 fence 的经典用法(配对模式)](#6.5 fence 的经典用法(配对模式))
    • [6.6 I/O 内存 vs Main Memory 的 ordering](#6.6 I/O 内存 vs Main Memory 的 ordering)
  • 七、原子操作深化
    • [7.1 LR/SC 的 reservation set](#7.1 LR/SC 的 reservation set)
    • [7.2 LR/SC 的使用约束(规范要求)](#7.2 LR/SC 的使用约束(规范要求))
      • [7.3 标准自旋锁(带约束)](#7.3 标准自旋锁(带约束))
    • [7.4 amo 的 aq/rl 位](#7.4 amo 的 aq/rl 位)
    • [7.5 原子操作 vs fence 的选择](#7.5 原子操作 vs fence 的选择)
    • [八、内存 ordering 决策总表(深化 06)](#八、内存 ordering 决策总表(深化 06))
    • [九、M-mode 程序员的内存清单](#九、M-mode 程序员的内存清单)
    • 十、本篇小结
    • 速查表

一、内存访问的完整路径(先建全景)

理解 PMA,先看一次 load/store 从 CPU 发出到内存,经过的全部层次:

复制代码
CPU 执行 load/store/amo 指令
   │  生成虚拟地址 VA(S/U 模式)或物理地址 PA(M 模式)
   ▼
┌─────────────────────────────────────────┐
│ ① 地址翻译(MMU,仅 S/U + satp≠Bare)     │  VA → PA
│    查 TLB → 未命中查页表 → 页 fault?      │  (mcause 12-15)
└─────────────────────────────────────────┘
   │  物理地址 PA
   ▼
┌─────────────────────────────────────────┐
│ ② PMA 检查(硬件,所有模式含 M)          │  ★最底层
│    内存类型?权限?原子性?缓存性?         │  (mcause 4/5/6/7)
└─────────────────────────────────────────┘
   │
   ▼
┌─────────────────────────────────────────┐
│ ③ PMP 检查(S/U 模式;M 模式看 L/mseccfg) │  软件配的围栏
│    该区允许 R/W/X?                       │  (mcause 1/3/5/7)
└─────────────────────────────────────────┘
   │
   ▼
┌─────────────────────────────────────────┐
│ ④ Cache 查找(D-Cache,若 cacheable)     │  命中则返回
│    miss → 总线访问                       │
└─────────────────────────────────────────┘
   │
   ▼
┌─────────────────────────────────────────┐
│ ⑤ 总线访问(AXI/AHB)                     │
│    到达 Main Memory 或 I/O 设备          │
└─────────────────────────────────────────┘

三个关键认知:

  1. PMA 在 PMP 之前 :PMP 检查的是"特权允不允许",PMA 检查的是"物理上能不能"。PMP 不能授予 PMA 没有的权限。
  2. M-mode 也受 PMA 约束 :M-mode 绕得过 PMP(默认),但绕不过 PMA------往 Empty 区发 load 照样 access fault,对不支持 amo 的 I/O 区发 amoadd 照样挂。PMA 是比 PMP 更底层的"物理事实"。
  3. PMA 硬件固定不可改(通常):不像 PMP 有 CSR 可配,PMA 由 SoC 设计连死,你只能查手册知道它,不能改它(少数核有 PMA 配置寄存器,软件可以配置)。

嵌入式视角:M-mode "全权"的边界

你在 06 篇读到"M-mode 全权访问物理内存"------这话要加限定:全权 = 不受 PMP 限制 ,但 PMA 这层你照样过 。你往一个没接外设的保留地址发读,硬件直接 access fault,M-mode 也救不了。调地址错类的死机,先怀疑 PMA(地址写错/区类型错),再怀疑 PMP。


二、PMA(Physical Memory Attributes)

PMA 是 RISC-V 内存模型最底层的一层,定义每个物理地址区域的属性。这是 RISC-V 规范 Privileged ISA 第 3.1 节的核心内容,也是你点名要补的。

2.1 PMA 是什么

  • PMA = 物理地址空间的属性表:把整个 PA 空间划分成若干区,每区有固定属性
  • 谁定义:SoC 设计者(芯片流片时连死),不是 CPU 核自己
  • 谁检查:PMA checker 模块(在总线和核之间),每次访存自动检查
  • 可改吗:通常不可;少数核(SiFive 等)有 PMA 配置寄存器可运行时改
  • 为什么需要:不同地址区物理性质不同------DDR 内存能缓存能推测访问,设备寄存器有副作用不能缓存不能推测,空区根本没东西。CPU 需要知道这些,才能用对的访问策略

厂商扩展:部分实现支持软件配置

但部分厂商在标准基础上做了扩展,确实允许软件配置 PMA:

芯来科技 :内核架构提供了 mattrii_base 和 mattrii_mask 寄存器,允许软件修改 PMA 属性,但有优先级规则------只能从低优先级属性修改为高优先级属性,不能反向放宽。

Andes V5:通过 CSR 寄存器(pmacfg + pmaaddr)支持软件配置 PMA,地址匹配模式类似 PMP 的 NAPOT 模式,Zephyr RTOS 中甚至提供了 CONFIG_SOC_ANDES_V5_PMA 配置选项来启用这一功能。

2.2 PMA 检查的属性清单

RISC-V 规范把 PMA 分"必须检测 "和"可选检测"两类:

属性 含义 必须? 你的关注点
Memory type Main Memory / I/O / Empty ✅ 必须 决定能不能缓存、能不能推测
R/W/X 权限 该区物理上可读/写/执行 ✅ 必须 PMA 级权限,与 PMP 不同
Misaligned 支持 该区支持非对齐访问? ✅ 必须 不支持则 misaligned fault
Atomicity (AMO) 支持 LR/SC?支持哪些 amo? 部分 多核同步、无锁队列关键
AMO 粒度 支持的最小 amo 访问大小 可选 4/8 字节?
Cacheability 可缓存? 可选 I/O 通常不可缓存
Coherence 在一致性域内? 可选 多核 DMA 一致性
Access size 支持的访问宽度 可选 8/16/32/64 位?
Memory model 该区遵循 RVWMO 还是强序 可选 I/O 强序,Main 遵循 RVWMO

2.3 Memory type 三类(最核心)

2.3.1 视角一:按"缓存行为"分(cache / no-cache / device)

类型 缓存 幂等 排序 典型用途
Cacheable(可缓存内存) ✅ ✅ 幂等 弱序(RVWMO) DDR / SRAM 主存
Non-cacheable(不可缓存内存) ❌ ✅ 幂等 弱序(RVWMO) DMA 缓冲、帧缓冲、写合并区
Device(设备内存) ❌ ❌ 非幂等 强序 UART/SPI/I2C 等外设寄存器

2.3.2 视角二:按"地址映射完整性"分(Main Memory / I/O / Empty)

PMA 把每个区分成三种内存类型:可缓存的主存区(遵循 RVWMO)、不可缓存的 MMIO 设备区(强排序、非幂等)、以及未映射的空洞区(访问触发 Access Fault)。

(1) Main Memory(主存)
  • 真 RAM(DDR/SRAM),无访问副作用
  • 遵循 RVWMO:可推测访问、可乱序、可缓存
  • 可缓存(cacheable),可预取
  • 典型:你的 SRAM 区 0x20000000、DDR 区
(2) I/O Memory(I/O 内存)
  • 设备寄存器区(MMIO),有访问副作用
  • 不遵循 RVWMO 的推测:不能推测读、不能合并写、不能重排(强序)
  • 不可缓存(non-cacheable)
  • 典型:UART/SPI/I2C 寄存器区 0x40000000
  • 访问大小受限(有的设备只支持 32 位访问,你读 8 位它挂)
(3) Empty(空区)
  • 没接任何东西的地址区
  • 任何访问 → access fault(mcause 5 load / 7 store)
  • 典型:memory map 里的 reserved 区

Memory type 决定一切下游行为

Memory type 是 PMA 的总开关:

  • Main Memory → 可缓存 + RVWMO + 可推测 + 可对齐优化
  • I/O → 不可缓存 + 强序 + 不推测 + volatile 语义
  • Empty → 直接 fault

你写 volatile uint32_t *reg = 0x40000000; 读 UART,编译器不敢优化掉,但硬件层面 保证它不推测不合并,靠的就是 PMA 标这区是 I/O。PMA 是 volatile 语义的硬件后盾。

2.4 PMA 触发的 fault

PMA 检查失败 → trap,具体 mcause:

情况 mcause 含义
访问 Empty 区 5(load)/ 7(store) load/store access fault
该区 PMA 不允许该操作(如 I/O 区不支持 amo) 5/7 access fault
非对齐访问且该区不支持 misaligned 4(load)/ 6(store) misaligned access fault
访问大小超该区支持(如 64 位访问 32 位设备) 5/7 access fault

2.5 PMA 怎么查 / 怎么知道

PMA 没有统一 CSR 可读全貌(这是 RISC-V 的一个痛点),获取方式:

  1. 查 SoC 手册的 memory map:手册会标每个地址区的类型/权限/缓存性。这是主渠道。
  2. 少数核有 PMA 配置寄存器:如 SiFive 的 PMA 配置 CSR,可读可改。看核手册。
  3. 探测法(危险):小心地访问试探,看是否 fault。生产代码别用。
  4. 厂商 BSP 提供:厂商通常给个 memory map 头文件/链接脚本,标好区属性。

2.6 PMA vs PMP(易混,必须分清)

PMA PMP
谁定 SoC 硬件(流片连死) M-mode 软件(CSR 配)
可改 通常不可(少数核可) 可,随时改
层级 更底层(物理事实) 更上层(特权围栏)
检查顺序 先 后
对 M-mode 生效(M 也绕不过) 默认不限(L 位/mseccfg 除外)
关系 决定"物理上能不能" 决定"特权上允不允许"

核心规则 :PMP 不能授予 PMA 没有的权限。

  • PMA 说某区不可写(如 ROM),你 PMP 配可写 → 写还是 fault(PMA 挡)
  • PMA 说某区不支持 amo,你 PMP 配可执行 amo → amo 还是 fault
  • 反过来:PMA 允许的,PMP 可以进一步限制(PMP 是收紧,不是放宽)

一句话记住 PMA/PMP

PMA 是物理事实(硬件定,M 也受限),PMP 是特权围栏(软件定,M 默认不受限)。PMP 只能在 PMA 允许的范围内收紧,不能放宽。

2.7 PMA 对你 M-mode 工作的实际影响

  1. 写裸机代码别乱访问地址:你以为 M-mode 全权,往保留区发读直接 fault。先确认 memory map。
  2. MMIO 访问要 volatile + 对齐 + 正确大小:PMA 的 I/O type 不让你推测/合并/乱序,volatile 保证编译器层面;对齐和大小要看设备 PMA 支持。
  3. 设备寄存器别用 amo:除非该 I/O 区 PMA 标支持 amo(少见),否则 amoadd 寄存器会 fault。设备寄存器用普通 read-modify-write。
  4. 调地址类 fault,先查 PMA:mtval 给地址 → 查 memory map 该地址 type/权限 → 确认你的操作是否被 PMA 允许 → 再看 PMP。

三、PMP 增强:ePMP / mseccfg

06 篇讲了基础 PMP(16 区 / TOR/NA4/NAPOT / L 位锁定)。这里讲 ePMP(enhanced PMP) 扩展,这是安全场景(功能安全/安全启动)的关键。

3.1 基础 PMP 的局限(回顾 + 暴露问题)

基础 PMP 的问题:

  • M-mode 默认不受限:PMP 只管 S/U,M-mode 全权。安全场景下,M-mode 代码跑飞能写任何区,PMP 拦不住。
  • L 位锁定后不可逆:一旦设 L,该区配置锁死,调试也不方便。
  • 默认允许:没配 PMP 的区,所有人(含 S/U)默认可访问(白名单难做)。

3.2 mseccfg(Machine Security Configuration)CSR

ePMP 新增 mseccfg CSR,三个关键位:

位 名 作用
bit0 RLB(Rule Locking Bypass) 允许修改已 L 锁定的 PMP 区(调试用,生产慎开)
bit1 MMWP(Machine Mode Whitelist Policy) M-mode 默认拒绝,只允许 PMP 显式允许的区(白名单)
bit2 MML(Machine Mode Lock) 改变 PMP 语义,PMP 对 M-mode 也生效

3.3 MML:让 PMP 约束 M-mode

设 mseccfg.MML = 1 后,PMP 语义改变:

  • PMP 对 M-mode 也生效(不再默认全权)
  • pmpcfg 的 R/W/X 位语义变化:在 MML 模式下,R/W/X 组合表示"该区对 M-mode 的权限"
  • 典型用法:安全启动后设 MML,锁定 M-mode 代码区不可写,防 M-mode 被攻破后改自身代码

3.4 MMWP:M-mode 白名单

设 mseccfg.MMWP = 1 后:

  • M-mode 默认拒绝所有访问
  • 只能访问 PMP 显式配置允许的区
  • 这是"最小权限"原则的硬件实现,功能安全(ISO 26262)隔离用

3.5 ePMP 的 E 位(Entry)

pmpcfg 新增 E 位(原 A 位旁):

  • E=0:该区不参与匹配(禁用,但保留配置)
  • E=1:该区参与匹配
  • 配合 MML/MMWP,实现精细的 M-mode 权限控制

3.6 安全场景应用(AEGIX 方向)

嵌入式视角: 功能安全的内存隔离

要过 ISO 26262,功能安全要求"安全区"和"非安全区"内存隔离。ePMP 给你硬件基础:

  1. 启动早期配 PMP 划分:安全代码区、安全数据区、非安全区、外设区
  2. 设 mseccfg.MML=1:让 PMP 约束 M-mode 自身(防 M-mode 跑飞写安全区)
  3. 设 mseccfg.MMWP=1:M-mode 默认拒绝,白名单访问(最小权限)
  4. 锁定关键区 L=1:防运行时被改

这是基础 PMP 做不到的(基础 PMP 管不了 M-mode),ePMP 是功能安全芯片的必备。

3.7 ePMP 配置示例(安全启动后)

c 复制代码
// 假设:安全启动完成,要锁定 M-mode 只能跑安全代码
// 区0:安全代码区 0x0-0x40000,RX for M-mode,禁止写
pmpaddr0 = 0x40000 >> 2;
pmpcfg0 = (1<<3) /*L*/ | (2<<3) /*A=TOR,...*/ ;  // 详看核手册 MML 下的位语义

// 区1:安全数据区 0x40000-0x80000,RW for M-mode
pmpaddr1 = 0x80000 >> 2;
pmpcfg1 = ...;

// 开启 MML + MMWP,让 PMP 约束 M-mode 并白名单
csrs mseccfg, (1<<2) /*MML*/ | (1<<1) /*MMWP*/;

⚠️ MML/MMWP 一旦开,PMP 语义复杂,配错会把自己锁死(连代码都跑不了)。生产代码前要在仿真/JTAG 反复验证。


四、Cache 架构与 CMO 扩展

06 篇讲了 DMA 一致性的"flush/invalidate"概念,这里讲 cache 架构本身和标准 CMO 指令。

4.1 Cache 层次

复制代码
          ┌─────────────┐
   Hart 0 │ L1 I-Cache   │  私有,小(8-64KB),快(1-3 cycle)
          │ L1 D-Cache   │
          └──────┬───────┘
                 │
          ┌──────▼───────┐
          │  L2 Unified  │  共享(多核),大(128KB-1MB)
          └──────┬───────┘
                 │
          ┌──────▼───────┐
          │  L3 / LLC    │  多核共享,大(MB级)
          └──────┬───────┘
                 │
            Main Memory
  • MCU 可能无 cache:直接 SRAM,访问确定性好(实时性优)
  • 有 cache 的 MCU:性能好,但引入一致性问题
  • Write-through vs Write-back:写策略,影响一致性维护方式

4.2 Cache 问题(06 的概念深化)

问题 描述 后果
脏数据未下刷 CPU 写了 cache 没 write-back 到内存 DMA 读到旧数据
陈旧数据未失效 DMA 写了内存,CPU cache 还是旧值 CPU 读到旧数据
指令 cache 陈旧 改了内存里的代码,I-Cache 没更新 CPU 执行旧指令

4.3 Cache 维护操作(术语要分清)

操作 英文 干什么 用途
clean clean 脏行下刷到内存(写回) DMA 发送前,保证数据到内存
invalidate invalidate 丢弃 cache 行(不回写) DMA 接收后,强制 CPU 从内存读
flush flush clean + invalidate 既要下刷又要失效
zero zero 直接清零一 cache 行(绕过读) 分配大 buffer 时省读
prefetch prefetch 预取到 cache 优化性能

clean vs invalidate 别用反

  • DMA 发送前 (CPU 写完 buffer,DMA 要读):clean(下刷脏数据,让 DMA 看到新数据)
  • DMA 接收后 (DMA 写完 buffer,CPU 要读):invalidate (失效 cache,让 CPU 从内存读新数据)
    用反了:clean 当 invalidate 用,DMA 数据被旧 cache 覆盖;invalidate 当 clean 用,脏数据丢失。这是 DMA bug 高发点。

4.4 CMO 扩展(Zicbom / Zicboz / Zicbop)⭐

RISC-V 标准 Cache Management Operations 扩展(较新,看核是否支持):

扩展 指令 作用
Zicbom cbo.clean / cbo.flush / cbo.inval cache 行维护
Zicboz cbo.zero cache 行清零(绕过读)
Zicbop cbo.prefetch.i / .r / .w 预取
c 复制代码
// Zicbom 用法(cache block 通常 64 字节,要对齐)
// DMA 发送前
cbo_clean(buf);        // 或 asm: cbo.clean 0(%0) :: "r"(buf)

// DMA 接收后
cbo_inval(buf);        // 或 asm: cbo.inval 0(%0) :: "r"(buf)
  • CMO 操作单位是 cache block(Cacheline,通常 64 字节),地址要 block 对齐
  • 操作多个 block 要循环
  • 老芯片没 CMO :用厂商提供的 cache 维护函数(如 __flush_dcache_range),每家不同

4.5 DMA 一致性三种模式(深化 06)

模式 机制 软件负担 典型
Coherent interconnect 总线监听(snoop),CPU/DMA 自动一致 无,软件不管 高性能多核
Non-coherent + CMO 软件用 cbo.clean/inval 维护 中,要正确 flush/invalidate 多数 MCU
Non-coherent + non-cacheable buffer DMA buffer 映射成 non-cacheable(PMA 标) 低,但性能差(绕 cache) 简单 MCU

4.6 自修改代码与 fence.i

c 复制代码
// 写新代码到内存
memcpy(func_addr, new_code, size);
fence.i;              // 同步 I-Cache 和 D-Cache!必须
func_addr();          // 执行新代码
  • fence.i(Zifencei 扩展)保证:之前的 store 对后续指令 fetch 可见
  • 没它:I-Cache 还是旧代码,CPU 执行旧指令
  • JIT/动态代码/在线升级固件必用
  • 多核下还要 IPI 让其他核 fence.i(复杂)

五、MMU 深入(超越 06 的 Sv39 概念)

06 讲了 Sv39 三级页表的概念,这里讲细节:A/D 位管理、TLB、sfence.vma、大页。

5.1 页表模式全表

模式 VA 位数 页表级数 最小页 典型
Sv32 32 2 4KB rv32 Linux
Sv39 39 3 4KB rv64 Linux(最常用)
Sv48 48 4 4KB 大地址空间 Linux
Sv57 57 5 4KB 新,超大地址空间

satp.MODE 选哪个:0=Bare(无翻译)、8=Sv39、9=Sv48、10=Sv57。

5.2 页表项位段(Sv39,深化 06)

复制代码
PTE(64位):
  bit0:    V(valid)        该 PTE 有效
  bit1:    R(readable)     可读
  bit2:    W(writable)     可写
  bit3:    X(executable)   可执行
  bit4:    U(user)         U 模式可访问(S 模式看 SUM 位)
  bit5:    G(global)       全局(所有地址空间有效,TLB 不按 ASID 刷)
  bit6:    A(accessed)     被访问过
  bit7:    D(dirty)        被写过
  bit8-9:  RSW             软件可用位
  bit10-53: PPN            物理页号
  bit54-63: reserved
  • R/W/X = 000 且 V=1:这是"指针 PTE",指向下一级页表(不是叶子)
  • R/W/X ≠ 000 且 V=1:这是"叶子 PTE",直接给物理页(可在某级终止成大页)

5.3 A/D 位管理(06 没讲的关键)⭐

A 位(访问)和 D 位(脏)的更新有两种策略,选错会踩坑:

(1) 硬件管理

  • CPU 访问页时自动置 A ,写时自动置 D
  • 优点:软件简单
  • 缺点:需要硬件支持(看核手册)
  • 检查:menvcfg/satp 相关位,或核手册

(2) 软件管理

  • CPU 访问时若 A=0,触发 page fault(mcause 13 load page fault / 15 store page fault)
  • OS 在 fault handler 里:置 A 位、更新 PTE、返回重试
  • 写时若 D=0,触发 store page fault,OS 置 D
  • 优点:硬件简单
  • 缺点:软件要处理 fault,不处理就死循环(fault → 重试 → fault)

A/D 位是移植 OS 的高频坑

你若给 RISC-V 移植 RTOS/OS,页表 A/D 位策略不对会死循环:

  • 硬件不支持自动 A/D,你 OS 又没装 page fault handler → 每次访问触发 fault,handler 不处理 → 重试 → fault → 死循环
  • 或 OS 假设硬件自动 A/D,但该核是软件管理 → 同样死循环
    移植前查核手册的 A/D 策略,OS 代码配套。

5.4 TLB 与 sfence.vma

  • TLB(Translation Lookaside Buffer):页表翻译的 cache,核内
  • 改了页表,TLB 可能还是旧的 → 要刷 TLB
  • sfence.vma(S-mode 指令):刷 TLB
asm 复制代码
sfence.vma              # 刷所有 TLB(所有地址、所有 ASID)
sfence.vma vaddr        # 刷特定虚拟地址的 TLB
sfence.vma vaddr, asid  # 刷特定 ASID 的特定地址
sfence.vma zero, asid   # 刷特定 ASID 的所有地址
  • 改一个 PTE → sfence.vma vaddr(精细,快)
  • 切进程(改 satp)→ 通常 sfence.vma(全刷)或用 ASID 避免全刷
  • M-mode 也能执行 sfence.vma(虽然是 S 级指令,M 全权)

5.5 ASID(Address Space ID)

  • satp 里有 ASID 字段(16 位)
  • TLB 项标记 ASID,不同进程的 TLB 项靠 ASID 区分
  • 切进程时若 ASID 不同,TLB 项不冲突,不用全刷(只刷旧 ASID 的)
  • 优化:减少切进程的 TLB 刷新开销

5.6 大页(Superpages)

Sv39 页表三级,可在任一级终止成大页:

终止级 页大小 用途
第3级(最底) 4KB 普通页
第2级 2MB 大页(减少页表项)
第1级 1GB 巨页(极少用)
  • 大页 PTE:在该级 R/W/X ≠ 000(叶子),直接给物理页
  • 优点:减少页表层级、省 TLB 项、减少翻译开销
  • 用途:大 buffer(DMA buffer、帧缓冲)用大页,提升 TLB 命中率

5.7 satp 切换与模式切换

  • M → S 切换:M-mode bootloader 配好页表,设 satp,mret 到 S-mode
  • satp 切换 = 地址空间切换:进程切换时改 satp
  • M-mode 不用 satp:M-mode 访问物理地址,M-mode 下 satp 被忽略

六、RVWMO 形式化

06 讲了"什么场景要 fence",这里讲 RVWMO 的形式化模型------三公理、PPO、fence 位详解。这是能看 spec 原文的深度。

6.1 RVWMO 的三层模型

RVWMO(RISC-V Weak Memory Ordering)用三个概念定义:

  1. Preserved Program Order (PPO):单个 hart 内,哪些内存操作对其他 hart 必须"保序"(其他可重排)
  2. Global Memory Order (GMO):所有 hart 的所有内存操作的一个全局全序(理论存在)
  3. Load Value Axiom:load 能读到哪些值

6.2 PPO(保序规则)

单个 hart 内,以下内存操作对按程序序保序(其他可重排):

规则 操作对 说明
Overlapping-address load→load / store→store / load→store(同地址) 同地址必须保序
Explicit sync fence 前后 fence 强制保序
Syntactic dependency op1 → op2(op2 依赖 op1 的地址/数据/控制) 数据依赖保序
Pipeline dependency op1 → op2(执行依赖) 流水线依赖
... (规范列出十余条)

核心:没有这些关系的操作,硬件可重排。这就是为什么多核共享数据要 fence------你的 store data 和 store flag 没有 PPO 关系,硬件可能先 store flag。

6.3 三公理

RVWMO 由三条公理定义(规范原文):

(1) Load Value Axiom(取值公理)

每个 load 读到的值 = GMO 中最新的以下之一:

  • 该 hart 自己 prior store 的值(按程序序)
  • 其他 hart 的 store 的值(按 GMO)
    即:load 不会凭空读到未写的值,但可能读到"非最新"(因为重排)。

(2) Atomicity Axiom(原子性公理)

对同一地址的 LR/SC 对:若 SC 成功,则 GMO 中该 SC 的 store 与对应 LR 之间,没有其他 hart 对该地址的 store。这保证 LR/SC 的原子性。

(3) Progress Axiom(前进公理)

系统不会无限挂起(没有 livelock)。这保证多核代码最终能前进。

6.4 fence 指令位详解(06 跳过的细节)

复制代码
fence <predecessor>, <successor>

predecessor / successor 可选(组合):
  r: memory read         (普通内存读)
  w: memory write        (普通内存写)
  i: device input        (设备读,即 MMIO load)
  o: device output       (设备写,即 MMIO store)

⚠ 注意:fence 的 i 是 "input"(设备读),不是 "instruction"!
       指令同步用单独的 fence.i

常用组合:

fence 含义
fence rw, rw 内存读写全屏障(最常用)
fence r, r 读读屏障(配对读 flag + 读 data)
fence w, w 写写屏障
fence rw, ow 内存读写 → 设备写(配 DMA 启动)
fence iorw, iorw 最强,含设备 IO 全屏障
fence.i 指令同步(I-Cache 与 D-Cache 同步),单独指令

fence predecessor, successor 语义:predecessor 之前的内存操作,对 successor 之后的内存操作可见。

6.5 fence 的经典用法(配对模式)

多核发布-等待模式:

c 复制代码
// Hart 0(生产者)
data = 42;
fence rw, rw;       // 保证 data 写入先于 flag
flag = 1;

// Hart 1(消费者)
while (flag != 1) ;
fence r, r;          // 保证先读 flag 再读 data
x = data;            // 一定能读到 42

没 fence:Hart 0 可能先写 flag 再写 data(重排),Hart 1 看到 flag=1 时 data 还没写。

6.6 I/O 内存 vs Main Memory 的 ordering

  • Main Memory:遵循 RVWMO,可重排、可推测、可合并(受 fence 约束)
  • I/O Memory :PMA 标记为 I/O,强序(不重排、不推测、不合并),fence 通常不需要额外加(I/O 访问本身强序)
  • 但 I/O 和 Main 混合操作时,可能仍需 fence(如先写 Main buffer,再写 I/O 寄存器启动 DMA)

七、原子操作深化

06 讲了 LR/SC 自旋锁的基本用法,这里讲 reservation set、SC 失败条件、amo 的 aq/rl 位。

7.1 LR/SC 的 reservation set

  • LR(Load-Reserved):加载并标记一个"保留集"
  • 保留集的范围由实现定(通常一个 cache 行,64 字节)
  • SC(Store-Conditional):若保留集未被打破,存储成功(返回 0);否则失败(返回非 0)
  • SC 失败条件 :
    • 另一个 hart 写了保留集内任意地址
    • 另一个 hart 的 LR 到同地址(部分实现)
    • 该 hart 执行了另一条 LR
    • 保留集"过期"(实现相关,如时间或指令数)

7.2 LR/SC 的使用约束(规范要求)

RISC-V 规范对 LR/SC 序列有约束(否则可能 livelock):

  • LR/SC 之间指令数有限(规范给上界,如 16 条)
  • LR/SC 之间不能有另一 LR
  • LR/SC 之间不能有对同地址的 store
  • LR/SC 之间最好别有分支(或分支要小心)

违反约束 → SC 可能永远失败(livelock)。

7.3 标准自旋锁(带约束)

c 复制代码
void spin_lock(int *lock) {
    while (1) {
        // 等待锁释放(普通读,避免太多 LR)
        while (*(volatile int*)lock != 0) ;
        // 尝试获取
        int got;
        asm volatile(
            "lr.w.amo.aq %0, (%1)\n"     // LR with acquire
            : "=r"(got) : "r"(lock));
        if (got == 0) {
            // 拿到了,但还要 SC 确认
            asm volatile(
                "sc.w.amo.rl %0, %2, (%1)\n"  // SC with release
                : "=r"(got) : "r"(lock), "r"(1));
            if (got == 0) return;  // SC 成功
        }
        // SC 失败,重试
    }
}

7.4 amo 的 aq/rl 位

amo 指令有两个内存排序位:

  • .aq(acquire):该 amo 之后的内存操作不能重排到它之前
  • .rl(release):该 amo 之前的内存操作不能重排到它之后
  • .aqrl:acquire + release(最强)
asm 复制代码
amoadd.w.aqrl rd, rs2, (rs1)   # 原子加,acquire+release
  • 用 aq/rl 实现锁,不用额外 fence
  • 不带 aq/rl 的 amo 也有原子性,但不保序(多核要小心)

7.5 原子操作 vs fence 的选择

场景 用什么
单条原子操作(计数器) amo(可带 aq/rl)
临界区进入/退出 LR/SC + aq/rl(锁)
非原子的发布-等待 fence rw,rw
设备 IO fence rw,ow(或 I/O 自带强序)

八、内存 ordering 决策总表(深化 06)

覆盖所有场景的决策矩阵:

场景 要不要屏障 用什么 原因
单核普通代码 不要 --- 对自己程序序执行
单核自修改代码 要 fence.i I-Cache 同步
多核共享数据发布 要 fence rw,rw 或 amo.aqrl 保 data→flag 序
多核共享数据读取 要 fence r,r 保 flag→data 序
多核临界区 要 LR/SC.aqrl 锁的 acquire/release
多核原子计数 可选 amoadd.aqrl 保序 + 原子
DMA 发送前 要 cbo.clean + fence rw,ow 数据到内存 + 对 DMA 可见
DMA 接收后 要 cbo.inval CPU 从内存读新数据
设备寄存器写后读 看 PMA I/O 强序通常不用 PMA I/O type 强序
改页表后 要 sfence.vma 刷 TLB
切进程(改 satp) 要 sfence.vma(或 ASID) 刷旧地址空间 TLB
中断返回 不要 mret 隐含 mret 有顺序保证

九、M-mode 程序员的内存清单

把本篇收束成你能用的清单:

层 你能改? 你必须懂 你的活
PMA 通常不能(少数核可) ✅ 必须 查手册知道区属性,别乱访问
PMP ✅ 能(CSR) ✅ 必须 划权限围栏给 S/U
ePMP/mseccfg ✅ 能 安全场景必须 AEGIX 功能安全隔离
Cache 策略 PMA 定,操作 CMO ✅ 必须 DMA 一致性维护
MMU/页表 ✅ 能(若跑 OS) 概念必须 bootloader 配 satp 切 S
TLB 间接(sfence.vma) 跑 OS 要懂 改页表后刷
fence/fence.i ✅ 用指令 ✅ 必须 多核/自修改代码保序
amo/aq/rl ✅ 用指令 多核要懂 同步原语

一句话总结本篇

PMA 是物理事实(硬件定,M 也受限),PMP/ePMP 是特权围栏(软件定,可约束 M),Cache/MMU 是性能/隔离机制(fence/sfence 是你操控它们的接口)。你的内存能力 = 懂 PMA 边界 + 配 PMP + 用 fence/CMO 维护一致性 + 必要时配 MMU。


十、本篇小结

  • PMA 是最底层:物理地址属性(Memory type/RWX/原子性/缓存性),硬件固定,M-mode 也受限;PMP 不能授予 PMA 没有的权限
  • PMA 三类:Main Memory(RVWMO/可缓存)、I/O(强序/不缓存/有副作用)、Empty(fault)
  • ePMP/mseccfg:MML(约束 M-mode)、MMWP(白名单)、RLB(解锁),功能安全隔离用
  • Cache 维护:clean(下刷)/invalidate(失效)/flush(两者)/zero(清零),CMO 扩展(Zicbom)标准化
  • DMA 一致性三模式:coherent interconnect / non-coherent + CMO / non-cacheable buffer
  • MMU 细节:Sv39 三级页表,A/D 位有硬件/软件两种管理(移植坑),TLB 用 sfence.vma 刷,大页省 TLB
  • RVWMO 三公理:Load Value / Atomicity / Progress;PPO 定义单 hart 保序规则
  • fence 位 :r/w/i(device input)/o(device output),fence pred, succ;fence.i 同步 I-Cache
  • 原子深化:LR/SC reservation set + 失败条件 + 使用约束;amo 的 aq(acquire)/rl(release)位替代 fence
  • 决策表:单核不用、多核用 fence/amo.aqrl、DMA 用 CMO+fence、改页表用 sfence.vma

速查表

想干啥 怎么做
查某地址物理属性 查 SoC 手册 memory map(PMA)
某地址访问 fault 查 mtval → memory map → PMA 是否允许该操作
M-mode 也被拦? 是,PMA 对 M 生效;PMP 默认不拦 M
约束 M-mode 自己 设 mseccfg.MML=1
M-mode 白名单 设 mseccfg.MMWP=1
DMA 发送前 cbo.clean(buf) + fence rw,ow
DMA 接收后 cbo.inval(buf)
改代码后执行 fence.i
多核发布-等待 fence rw,rw(写侧) + fence r,r(读侧)
多核临界区 LR/SC.aqrl 自旋锁
多核计数器 amoadd.w.aqrl
改页表后 sfence.vma
切进程 sfence.vma(或用 ASID 避免全刷)
页表 A/D 位 查核手册是硬件还是软件管理,配套 OS
I/O 寄存器访问 volatile + 对齐 + 正确大小 + 别用 amo

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

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


相关推荐
CAD开发小慧2 小时前
MFC工业软件换上4K屏后界面模糊?BCGControlBar Pro如何适配高DPI与多屏
架构
qwfy2 小时前
我要用 AI-native 架构,从零重建一套制造业 ERP/MES
vue.js·spring boot·架构
航飞光电市场经理3 小时前
人员定位系统“全栈自研”技术解析:从射频前端到定位引擎的架构拆解
前端·架构
szxinmai主板定制专家3 小时前
RK3576+CODESYS+RK182X+FPGA异构架构:半导体精密设备一体化控制器方案
人工智能·fpga开发·架构·rk3576+codesys
szxinmai主板定制专家3 小时前
半导体设备控制器:RK3576+CODESYS+RK182X异构架构,实现晶圆视觉检测与运动联动闭环
人工智能·fpga开发·架构·视觉检测·边缘计算·codesys·rk3576
落魄实习生4 小时前
Agent Scope Java 2.x 系列【1】核心架构
java·开发语言·架构
安易算力4 小时前
智算中心网络架构选型:RDMA、InfiniBand与以太网技术深度解析
网络·架构
AOI小白新手上路5 小时前
韦东山《ARM 架构与编程》3-2 GPIO 引脚操作方法概述 · 学习笔记
arm开发·学习·架构
其实防守也摸鱼5 小时前
内网安全防护实践:从攻击视角理解防御要点
java·前端·安全·架构·自动化·nps