计算机体系结构 21:页故障与映射修改
计算机体系结构 21:页故障与映射修改
核心问题
上一篇把地址翻译分成三类结果:TLB miss 后成功、权限失败产生 page fault、翻译成功后再进入数据缓存。本篇继续追踪 page fault 之后发生什么。
一个页故障不是“硬件把问题修好”。硬件报告故障信息,软件决定要不要修复映射、如何更新页表、何时让当前 hart 的旧翻译失效、是否通知其他 hart。本文用一个有限状态轨迹回答三个问题:
- page fault 报告了什么。
- 软件补映射后,为什么还要本地
SFENCE.VMA。 - 多 hart 场景下,为什么本地 fence 不等于远端 shootdown。
异常处理要区分“报告、修复、重试、广播”。硬件只完成报告和控制流转移;页表修改、失效范围和远端协调是软件协议。
边界
一手依据仍然是 RISC-V Privileged Architecture 的 supervisor 规范。2026-09-22 通过可读官方 supervisor 页面核对到 Supervisor-Level ISA 1.13;固定版本 URL 当前不可直接读取,因此正文引用可读官方 URL,不把固定版本 URL 当作本轮已访问证据。相关点包括 stvec、sepc、scause、stval、sstatus 以及 SFENCE.VMA。规范说明 satp 写入不自动提供页表更新与后续地址翻译之间的排序,也不自动清空地址翻译缓存;SFENCE.VMA 用来让当前 hart 后续隐式页表读取观察到内存中的管理结构更新。其他 hart 需要额外协调。
本文证据来自同一个有限 Python 模型:
输出附件:
sv32_trace.json、sv32_model_stdout.json
从仓库根目录复跑:
1 | |
证据等级:功能执行。
这不是实机内核陷入记录。模型没有真实 stvec 跳转、没有保存通用寄存器、没有页分配器、没有调度器、没有 IPI,也没有远端 hart 确认回执。sepc 在本文中只作为规范概念说明;模型没有执行完整指令流,因此输出中不伪造 sepc 数值。
陷入入口与上下文边界
同步 page fault 和异步中断都进入 supervisor trap 机制,但原因不同。page fault 由当前指令的访存或取指触发,软件通常需要让 sepc 指向相关指令,以便修复后重试或报告错误;异步中断来自外部事件或计时器,不代表当前指令本身地址翻译失败。
stvec 决定 trap 入口基址和模式。Direct 模式下所有 trap 进入同一个入口;Vectored 模式下,中断可按 cause 偏移到不同入口,同步异常仍进入基址入口。进入 S 模式 trap 时,sstatus 中的 SPP 记录先前特权级,SPIE/SIE 保存和关闭中断使能状态;返回时由 sret 恢复相关状态并回到 sepc。
硬件不会自动保存全部通用寄存器。真正的内核入口需要软件保存将要破坏的 GPR,建立内核栈或 trap frame,再调用具体处理例程。本文模型只记录 scause、stval 和教学 reason,不模拟 stvec 分派、GPR 保存、sret 或异步中断。
规范允许 stval 在某些场景为 0;因此本文 JSON 中的故障 VA 只是本模型给出的 informative trap value,不能写成所有平台所有 page fault 都必定提供非零故障地址。
页故障报告的是“这次访问不成立”
模型中的缺页地址是:
1 | |
根页表有 level-1 非叶子 PTE,指向二级页表 PPN 0x220,但二级叶子 PTE 缺失。第一次 load 产生:
1 | |
这里的信息可以分层读:
| 字段 | 含义 |
|---|---|
scause=load_page_fault |
触发访问类型是 load |
stval=0x00c00008 |
本模型提供的故障相关虚拟地址;规范允许某些情况为 0 |
reason=level0_invalid_or_nonleaf |
教学模型给出的内部分类 |
pte_address=0x220000 |
本次遍历需要检查的二级 PTE 地址 |
真实硬件不需要给出本文这种 reason 字符串。它会按规范报告 trap cause 和 trap value;操作系统再结合页表、VMA、权限和进程状态判断后续动作。
page fault 是“软件处理入口”,不是“错误类型的终点”。同样是 page fault,可能是 demand paging、写时复制、栈增长、权限违规,也可能是非法地址。
修复映射后需要让当前 hart 忘掉旧翻译
软件决定修复这个缺页时,会写入一个新的叶子 PTE。模型把缺失页补成:
1 | |
随后执行本地 SFENCE.VMA:
1 | |
before 和 after 都是 3,并不矛盾。这个 VA 之前没有成功翻译项,所以本地 TLB 中没有对应条目可删。SFENCE.VMA 的意义不只是“删掉一个一定存在的条目”,还在于把页表内存更新与后续隐式页表读取排好序。
重试同一 load 后得到:
1 | |
这个轨迹的顺序是:
1 | |
它只证明模型里的软件修复路径可执行,不证明真实操作系统必然为每个 page fault 立即分配物理页。真实系统还要考虑 VMA 是否存在、权限是否允许、是否需要从磁盘读页、是否触发 OOM、是否是写时复制等。
修改已有映射时,本地成功不代表远端成功
页故障修复是“从无到有”。另一种更容易出错的情况是“已有映射被改写”。模型设置两个 hart 先访问同一个 VA:
1 | |
修改前:
1 | |
1 | |
两个 hart 都缓存了旧翻译。随后 hart0 修改 PTE,把 PPN 从 0x330 改成 0x331,只执行本地 SFENCE.VMA。重试结果是:
1 | |
hart0 已看到新翻译,hart1 仍命中旧翻译。这个反例说明:本地 fence 的范围是当前 hart。要让其他 hart 也停止使用旧翻译,需要系统软件发起远端失效协议,并等待足够的确认点。
“改了页表”不是全系统事实;只有参与协议的 hart 才会按协议边界观察到新映射。多核正确性要问“谁知道、何时知道、是否确认”。
权限故障不是映射缺失
本文模型里还有两类权限 fault:
1 | |
只读页被 store,修复方式不是简单补一个 PTE。软件必须判断这是不是合法写时复制、是否允许升级权限,或者应该向进程报告错误。
另一个例子是 U 模式访问 supervisor-only 页:
1 | |
这说明 fault 可以发生在 TLB hit 路径上。处理这类 fault 时,不能把它和“二级 PTE 缺失”混在一起。
本篇验收
- 能解释
scause、stval在 page fault 中的作用,并说明本文没有伪造sepc执行流。 - 能按顺序复述缺页修复轨迹:fault、软件写 PTE、本地
SFENCE.VMA、重试。 - 能说明
satp或 PTE 写入不自动完成旧翻译失效。 - 能用两个 hart 的反例说明“本地 fence 不等于远端 shootdown”。
- 能把映射缺失、权限 fault、远端 stale translation 分开处理。
练习
练习 1
VA=0x00c00008 第一次 load 时二级 PTE 缺失。软件补上 PPN 0x320 的 R/W 叶子 PTE 后,重试得到的 PA 是多少?
答案:offset 是 0x8,所以 PA 是:
1 | |
练习 2
hart0 和 hart1 都缓存了 VA=0x01000000 -> PA=0x330000。hart0 把页表改成 PPN 0x331 并只执行本地 SFENCE.VMA。hart1 下一次 load 必然看到新 PA 吗?
答案:不必然。本文模型中 hart1 仍命中旧 TLB 项,得到 0x330000。本地 SFENCE.VMA 只处理当前 hart;远端 hart 需要额外 shootdown 协议。






