地址翻译

RISC-V Sv39地址翻译、PTW状态机与TLB实现

Posted by CloudingYu on May 11, 2026

一、背景说明

以下内容为 RISC-V 处理器内存管理单元(MMU)设计中的实践经验分享,重点关注相关概念和工程实现问题。


二、SATP 寄存器与 Sv39 地址翻译

2.1 SATP 寄存器结构

SATP(Supervisor Address Translation and Protection)是 RISC-V 中控制地址翻译的核心 CSR 寄存器(64 位 Sv39 模式):

字段 位宽 说明
MODE 高 4 位 地址翻译模式,Sv39 下为 8(bare 为 0)
ASID 16 位 地址空间标识符,最多支持 65536 个地址空间
PPN 低 44 位 第一级页表的物理页号(基地址)

一次写入需将整个 64 位 SATP 值写入。PPN 乘上页大小(4KB)即得到第一级页表在物理内存中的起始地址。

2.2 WARL 机制

WARL(Write Any, Read Legal):对某些 CSR 字段,写入任意值都不会报错,但读回时永远返回合法值。对于不支持的写入值,硬件保持原有值不变。学校 Lab 4 中 mask 的概念与此一致——只有经过 mask 的数据才能真正写入寄存器。

之所以设计 WARL,是为了规范 CSR 的权限检查——硬件不必对每个非法写入都报异常,简化了实现。

2.3 MODE 和 ASID

  • Sv39 模式下:MODE 字段只有 0(bare,无地址翻译)和 8(Sv39)两种合法值
  • 可通过 mask 只允许第 3 位可更改(0 ↔ 8)
  • 仅在 S 模式(Supervisor/内核模式)或 U 模式(User/用户模式)下 SATP 激活
  • ASID 用完(16 位耗尽):需回收旧 ASID,此时必须执行 SFENCE.VMA 来刷掉旧 ASID 对应的 TLB 条目

三、Sv39 多级页表遍历(PTW)

3.1 为什么需要多级页表

RISC-V Sv39 使用 39 位虚拟地址(27 位 VPN + 12 位页内偏移),44 位物理地址(34 位 PPN + 12 位页内偏移)。

如果直接做 27 位到物理地址的一对一映射,需要一个 $2^{27}$ 条目的大表——约 128MB,远超可接受范围。内存才多大?这表放进去直接就疯了。

解决思路:分级。Sv39 将 27 位 VPN 分成三个 9 位片段(VPN[2]、VPN[1]、VPN[0]),每级 512 个条目($2^9 = 512$),构成三级页表

3.2 三级页表遍历流程

  1. 从 SATP.PPN 获得第一级(L2)页表基地址
  2. 用 VPN[2](高 9 位)在 L2 页表中索引 → 得到 L1 页表的 PPN
  3. 用 VPN[1] 在 L1 页表中索引 → 得到 L0 页表的 PPN
  4. 用 VPN[0] 在 L0 页表中索引 → 得到最终物理页框的 PPN
  5. PPN + 页内偏移 = 最终物理地址

核心思想:用多级索引将 $O(2^{27})$ 的大表问题转化为三级 $O(512)$ 的小表问题——相当于取了一个 log 运算。程序实际使用的地址空间往往稀疏,多级页表只需要维护实际使用到的部分。

3.3 页大小与对齐

Sv39 使用 4KB 页($2^{12}$ 字节),因此虚拟地址低 12 位和物理地址低 12 位完全相同(页内偏移无需翻译)。地址翻译实质上是在虚拟页物理页之间建立映射。


四、PTW 状态机设计

4.1 状态定义

PTW(Page Table Walker)状态机设计:

状态 含义
IDLE(空闲) PTW 空闲,等待翻译请求
WAIT(等待) 已向总线发出内存请求,等待总线返回 PTE 数据
PROCESS(处理) 总线返回数据后,分析 PTE 是否合法、是否指向下一级页表、是否需要更新 A/D 位
DATA_RETURN(完成) 三级页表遍历完成,返回最终翻译结果给流水线
FAULT_RETURN(异常返回) 翻译过程中出现异常,将虚拟地址、PTE 和异常信息返回流水线

4.2 关键设计细节

内存请求重发:总线同时只能处理一个请求。有可能 MMU 发出请求时总线忙(bus full)——此时请求实际上没有发出去,需要重发。因此从 IDLE 到 WAIT 之间需要重发判断:如果总线忙,就一直在 IDLE 等待。

级数计数:需要记录当前访问到第几级页表。如果已经到了最后一级(L0),但 PTE 还指向下一级页表,应报异常(页表遍历终止条件)。

三级页表的遍历顺序:没有 TLB 时,必须依次访问三级页表,共三次内存访问。三次访问可能被中断(总线被占用),此时状态机保留当前状态,等待重发。

关于访问三次内存能否保证不被中断:在简化实现中可以停掉流水线前端的取指,保证连续三次访问。但实际情况中可能被打断——总线满就打回去重发;如果是一般中断(指令集层面),需要从头再来。状态机的存在就是为了记录当前状态以应对这些情况。

4.3 A/D 位更新

  • A 位(Accessed):任何页访问时硬件自动置 1
  • D 位(Dirty):store 操作时硬件自动置 1

更新 A/D 位时,需要将从内存读到的 PTE 整个修改后直接写回内存(不是只写到 TLB)。因为 TLB 是只读的,不维护反向同步。

一旦页表被更新(A/D 位修改),RISC-V 规范要求执行 SFENCE.VMA 来刷新相关的 TLB 条目。因此硬件不需要在内存和 TLB 之间进行一致性维护——TLB 只管缓存,页表修改后通过 SFENCE.VMA 整体失效即可。


五、TLB 与 SFENCE.VMA

5.1 TLB 基本概念

TLB(Translation Lookaside Buffer):页表遍历的硬件缓存。当首次将虚拟地址翻译为物理地址后,将映射关系缓存到 TLB 中。后续访问同一页时不必重复遍历三级页表。

做了一次 MMU 地址翻译之后把结果存起来,以后同一页的访问直接查 TLB 就行——不需要再多次访问内存。

5.2 TLB 的设计特性

  • 只读:TLB 不写回内存。因为页表修改后通过 SFENCE.VMA 主动刷掉,无需反向同步
  • 每核私有:TLB 不在多核之间共享,简化了一致性维护
  • 覆盖范围:如果物理内存空间较小,TLB 可以完全覆盖所有物理页,此时 MMU 几乎只需访问 TLB

TLB 只读这个设计,一旦要扩展到多核,难度马上暴增。

5.3 SFENCE.VMA 指令

SFENCE.VMA(Supervisor Fence Virtual Memory Address)用于刷新 TLB(使 TLB 条目失效)。

根据 rs1、rs2 的不同取值:

rs1 rs2 效果
X0 X0 失效所有地址空间中的所有虚拟地址映射
X0 ≠ X0 失效 rs2 指定地址空间中的所有映射
≠ X0 X0 失效所有地址空间中当前虚拟地址的映射
≠ X0 ≠ X0 失效 rs2 指定地址空间中指定虚拟地址的映射

反直觉的情况(rs1 ≠ X0, rs2 = X0):为什么一个虚拟地址会出现在多个地址空间中?一个典型例子是 printf 这样的库函数——内核可能将同一块物理内存映射到所有进程的地址空间中,使所有进程共享同一份代码。此时 PTE 的 G 位(Global)置 1。普通 SFENCE.VMA 不会失效 Global 映射,需要用 rs2 = X0 来刷除。


六、MXR 与 SUM 标志

6.1 MXR

MXR(Make eXecutable Readable):在 sstatus.MXR = 1 时,允许对仅允许执行(Execute-only)的页进行读取。反之(MXR = 0),read=0 但 execute=1 的页只能取值不能读取数据。

6.2 SUM

SUM(permit Supervisor User Memory access):在 sstatus.SUM = 1 时,允许 S 模式(内核)访问 U 模式(用户)的页。

使用场景:操作系统内核有时需要访问用户程序的内存空间(如系统调用参数传递),如果不置 SUM 位,硬件会触发异常。

6.3 PTE 的 U 位

PTE 中的 U 位:= 1 表示只有用户程序可访问此页,S 模式不可访问(除非 SUM = 1)。


七、异常处理机制

7.1 Trap = 异常 + 中断

RISC-V 将异常(Exception,同步事件,如缺页、非法访问)和中断(Interrupt,异步事件,如外设中断)统称为 Trap

7.2 异常处理的本质

异常处理看起来复杂,实质上就是写寄存器 + 跳转两件事。

  1. 写寄存器:将异常相关信息写入 CSR(发生了什么、在哪里发生、什么原因)
  2. 跳转:PC 跳转到异常处理程序的入口地址

7.3 关键 CSR 寄存器

寄存器 含义
xEPC(Exception PC) 发生异常的指令的 PC 值。异常处理完成后,通过 MRET/SRET 回到此地址重新执行
xCAUSE 异常原因编号(如 12 = 指令缺页,13 = Load 缺页,15 = Store/AMO 缺页)
xTVAL 与异常相关的虚拟地址(Load/Store 时为此指令访问的虚拟地址,取值异常时为此指令的虚拟地址)
xtvec(Trap Vector Base) 异常/中断处理程序的入口基地址

其中 $x$ 表示特权级别:M 为机器模式(Machine),S 为监管者模式(Supervisor)。

7.4 异常向量表与入口跳转

RISC-V 支持两种 Trap 跳转模式:

  • Direct 模式(异常):所有异常直接跳转到 xtvec 指定的基地址(PC = base)
  • Vectored 模式(中断):跳转到 base + 4 × cause(根据中断编号自动偏移)

为什么异常和中断分开?异常更紧急,需要统一入口处理;中断更多来自外设,用户可能需要自定义服务程序,因此支持向量化跳转。

7.5 与地址翻译相关的异常类型

操作 Page Fault Access Fault
取指(Instruction) 12 1
Load 13 5
Store/AMO 15 7
  • Page Fault:PTE 的 V 位 = 0(页不在内存,在磁盘中),由操作系统处理缺页
  • Access Fault:PTE 权限检查失败(PMA/PMP 违规等)

若同时满足多个异常条件,按优先级上报。


八、缓存与多核漫谈

8.1 L1/L2 Cache 的角色

L1 Cache 是每个核私有的,L2 是多核共享的。L1 miss 代价还能接受(去 L2 找),但 L2 miss 直接去主存性能下降非常大。所以 L2 宁可提高复杂度(全相联)也要确保命中率。

8.2 多核缓存一致性

MESI 协议:多核缓存一致性最经典的协议。

  • L1 私有 → 不同核的 L1 不能直接交换数据
  • L2 共享 → 多核通过 L2 实现数据同步
  • 内存一致性(Memory Consistency)在共享 L2 层面实现

多核一致性难度极大——一个人写一个人读还好,两个人一起写就得好好设计。后续课程会详细讲解 MESI 协议。

8.3 关于 AI 辅助硬件设计

大模型能辅助写硬件模块(如 ALU、Register File),但需要工程师分解好任务、定义好接口信号。不能让大模型”一句话设计一个 CPU”——必须把框架定好、约束给足、验证规则写清楚。大模型输出是概率性的,错的时候”冷不丁就错”,输出文本却”像模像样”——判断正确性很困难。


课程事务

  • 已实现的部分:五级流水线、缓存(含分支预测冲刷时的锁存问题修复)、类 TPU 矩阵乘法协处理器
  • Lab 涉及页表遍历(PTW)、TLB、CSR 特权指令、PMP/PMA 检查
  • 多核 MESI 的实验设计仍在讨论中(硬件实现挑战大,可能先做软件模拟)