介绍
在 Linux 内核的内存体系中,缺页异常与页表遍历是支撑虚拟内存运作的两大核心支柱,也是区分 “内核使用者” 与 “内核精通者” 的关键分水岭。很多人误以为缺页只是 “内存访问错误”,页表遍历只是简单的地址翻译流程,却忽略了它们背后深度耦合的硬件交互逻辑与内核实现细节。
从 CPU 的 MMU 硬件触发,到内核的缺页异常处理流程,再到四级页表(PGD→PUD→PMD→PTE)的逐级遍历,每一个环节都藏着内核设计的精妙与复杂,不懂这两者,就无法真正理解 Linux 如何高效管理内存、实现多进程隔离与按需调页。
缺页异常是 Linux 内核处理 “虚拟地址到物理地址映射缺失” 的核心机制,而页表遍历则是定位映射缺失、完成地址转换的必经路径。当 CPU 访问虚拟地址时,MMU 会先尝试 TLB 缓存,未命中则触发页表遍历;若遍历中发现页表项空缺、页面被换出到 swap 或权限不匹配,才会引发缺页异常。
内核会通过 find_vma 校验虚拟地址合法性,再根据 PTE 状态分发处理逻辑 —— 包括匿名页分配、写时复制(COW)、swap 页面加载等,整个过程依赖页表遍历精准定位问题节点。深入理解这一机制,不仅能看懂 exc_page_fault、handle_pte_fault 等核心函数的源码逻辑,更能掌握内核内存调优、故障排查的底层方法,这是掌握 Linux 内核内存管理的必备前提。
缺页异常的触发时机
当进程尝试访问虚拟地址时,若虚拟地址对应的物理页面不存在或权限不足,则会触发缺页异常。具体而言,页面不存在的情况通常发生在首次访问新分配的虚拟内存区域,或页面被换出到磁盘后再次访问时。此外,若进程试图以非法权限访问页面(例如写访问只读页面),也会导致缺页异常的发生。这些情况均表明虚拟地址到物理地址的映射尚未有效建立,从而需要通过缺页异常处理机制进行进一步的操作。
-
页面不在物理内存
这是最常见的产生缺页异常的原因。进程启动时,并不会将其所有的虚拟地址空间对应的页面都加载到物理内存中,而是采用按需加载的策略。只有当进程实际访问到某个虚拟页面时,操作系统才会尝试将其对应的物理页面加载进来。如果此时该物理页面尚未被加载,就会触发缺页异常。比如,一个程序在运行过程中,需要读取一个很大的文件,这个文件被映射到虚拟地址空间中。但一开始,文件的大部分内容都还在磁盘上,当程序访问到还未加载到内存的文件部分时,就会因为页面不在物理内存而产生缺页异常。 -
页面已被换出
在物理内存资源紧张时,操作系统会将一些暂时不用的物理页面换出到磁盘的交换空间(Swap Space)中,以腾出物理内存给更需要的页面使用。当进程再次访问这些被换出的页面时,就会发生缺页异常,因为此时页面不在物理内存中,需要从交换空间重新加载回物理内存。例如,系统中同时运行着多个大型程序,物理内存逐渐被占满,操作系统为了保证所有程序的正常运行,会将一些程序中不太常用的页面换出到磁盘。当这些程序后续又访问到这些被换出的页面时,缺页异常就会接踵而至。 -
映射关系未建立
即使页面在物理内存中,但如果虚拟地址到物理地址的映射关系没有正确建立,也会导致缺页异常。在进程初始化或者内存分配、释放的过程中,如果映射关系的更新出现错误,就可能出现这种情况。比如,在使用 mmap 系统调用进行内存映射时,如果映射过程中出现错误,没有成功建立起虚拟地址和物理地址之间的映射,那么当进程访问映射区域的虚拟地址时,就会因为映射关系未建立而触发缺页异常。
MMU 查页表的完整过程(ARM64)
以 EL0 访问虚拟地址 为例:
CPU 发出虚拟地址 VA
↓
MMU 查 TTBR0_EL1
↓
Level 0 → Level 1 → Level 2 → Level 3
↓
找到 PTE(页表项)
什么时候会“查页表失败”?
在任意一级页表中,MMU 可能发现:
| 情况 | PTE 状态 | 结果 |
|---|---|---|
| 页表项不存在 | PTE_VALID == 0 |
Translation fault |
| 权限不足(读/写/执行) | AP / UXN / PXN | Permission fault |
| 访问标志未置位 | AF == 0 | Access flag fault |
| 脏位未置位 | DBM == 0 | Dirty state fault |
所有这些,都叫 MMU fault
MMU 查页表失败是怎样变成一次同步异常的
MMU 在地址翻译过程中发现页表无效或权限错误 →
硬件自动触发一个“同步异常(synchronous exception)” →
CPU 跳转到 EL1 的同步异常向量el1_sync()
这不是软件主动 raise 的,而是 CPU 硬件行为,MMU 硬件电路直接触发一个同步异常。
1️⃣ 这是 CPU 的硬件行为
当 MMU 检测到 fault:
- CPU 立即停止当前指令执行
- 硬件自动:
- 保存异常原因到
ESR_EL1 - 保存出错地址到
FAR_EL1 - 切换到 EL1
- 跳转到 异常向量表
- 保存异常原因到
MMU fault
↓
CPU hardware
↓
EL1 sync exception
↓
el1_sync()
2️⃣ 为什么是“同步异常”?
因为:
- 由 指令执行直接触发
- 与指令 一一对应
- 必须返回 同一条指令重新执行
符合 ARM 对 synchronous exception 的定义。
ARM64 异常向量表里是谁在接?
异常向量表(vectors)中:
el1_sync:
// 同步异常入口
bl do_mem_abort
do_mem_abort() 会根据:
ESR_EL1(异常类型)FAR_EL1(出错地址)
决定如何处理。
内核是如何区分“缺页”还是“非法访问”?
1️⃣ ESR_EL1 中的 DFSC
DFSC (Data Fault Status Code)
例如:
| DFSC | 含义 |
|---|---|
| 0x4 / 0x5 / 0x6 |



被折叠的 条评论
为什么被折叠?



