page fault 与缺页异常
这页讲的是:程序访问一个地址时,并不代表那一页此刻已经老老实实摆在 CPU 面前。很多时候,内核正是借由 page fault 这次“访问没直接成功”的机会,去补齐映射、拉页进来、扩展栈、做写时复制,或者判定这次访问根本非法。学这页的重点,是理解缺页不等于出错,它常常是虚拟内存正式开始兑现承诺的入口。
这块是什么
page fault 与缺页异常,讲的是当 CPU 发现当前访问的虚拟地址不能直接完成时,怎样把控制权交给内核,由内核判断这是一次正常的按需建页、一次需要从磁盘补数据的缺页、一次写时复制触发,还是一次应该把进程打断的非法访问。它站在 CPU、地址空间、页表、页缓存和进程执行之间,是虚拟内存真正落到运行时的关键入口。
可以把它理解成:程序拿着房间号去开门,发现门后面现在还没准备好。内核这时会判断:是应该临时配钥匙、把家具搬进来、给你分一间新房,还是这扇门本来就不该让你开。
它负责什么
把虚拟地址兑现成可访问内存
- 访问地址时发现页表项还没准备好
- 按需建立映射,而不是一开始就把所有页都铺满
- 让大地址空间和惰性分配真正可行
- 把“地址存在”和“物理页就绪”这两件事分开
承接按需装入与写时复制
- 文件页可能要在第一次访问时才真正进内存
- 匿名页可能在写入时才从共享状态分裂出来
- 把 fork 之后的复制成本延后到真正写的时候
- 让内存利用率和启动速度更可控
识别非法访问并终止错误路径
- 区分“正常缺页”和“越界/权限不对”的访问
- 在错误场景下触发异常处理或进程终止
- 帮助系统守住地址空间边界
- 让错误不是默默污染别人的内存
为什么它不是“内存不够时才会发生”
| 如果想得太简单 | 会怎样 | 真正关键在哪 |
|---|
| 把 page fault 理解成纯粹报错 | 会错过它其实是虚拟内存正常工作的一部分。 | 很多缺页本来就是按需映射和按需装入的正式入口。 |
| 觉得只有内存紧张才会缺页 | 会忽略首次访问、COW、文件页装入也都会走进来。 | 缺页关心的是“当前访问能不能直接完成”,不只是“系统够不够内存”。 |
| 只盯着页表不看进程执行 | 容易看不见缺页会让任务阻塞、唤醒甚至收到错误结果。 | 这既是内存问题,也是执行路径和调度问题。 |
| 把所有缺页都当一种事 | 会把匿名页、文件页、写时复制和非法访问混在一起。 | 不同缺页背后代表完全不同的系统语义。 |
关键概念
| 概念 | 现在怎么理解 |
|---|
| page fault | CPU 访问地址时发现当前映射不能直接完成,于是把处理权交给内核。 |
| 缺页异常 | 更偏中文语境下的说法,既可能是正常按需补页,也可能是非法访问。 |
| 按需分页 | 不是一开始把一切都准备好,而是访问到了再建立或装入。 |
| 写时复制 | 先共享、等真正写入时再拆分出私有页的策略。 |
| 匿名页 / 文件页 | 前者更像进程自己的内存内容,后者更像来自文件映射或页缓存的数据来源。 |
| 权限错误 | 页可能存在,但当前访问方式不被允许,比如该只读却去写。 |
为什么重要
- 它解释了为什么虚拟内存不是“先分完,再访问”,而常是“先答应地址空间,再按需兑现”。
- 很多程序首次触碰大块内存时的停顿、文件映射的首次访问成本、fork 后写入开销,背后都和缺页路径直接相关。
- 很多看似“程序突然崩了”的问题,本质上是访问到了不该访问的地址或权限不对。
- 理解这层后,你会更容易把地址空间、页表、页缓存、调度等待和异常结果串成一条真实运行路径。
常见误解
- 误解一:page fault 一发生就说明程序坏了。实际上很多缺页是完全正常的运行时行为。
- 误解二:缺页只是内存管理内部细节。实际上它会影响任务延迟、I/O、写时复制和进程最终命运。
- 误解三:有地址就等于有物理页。实际上地址空间承诺和真正可访问页之间常隔着一整套按需机制。
它不负责什么
- 它不等于全部内存管理;页分配、回收、写回和地址布局仍是更广的体系。
- 它不自动说明根因;发生缺页后,背后可能是正常装页、权限错误、映射缺失或对象已失效。
- 它不单独决定性能;缺页代价还会受到 I/O、局部性、回收压力和调度状态影响。
和其他模块的关系
| 相关模块 | 关系 |
|---|
| 页表与地址空间 | 那页讲地址如何组织,这页讲访问失败时系统怎样把那套组织兑现成可运行结果。 |
| 页缓存、回收与内存压力 | 文件页缺页常会和页缓存装入、回收后重建、内存压力下的抖动连在一起。 |
| 调度器与等待 | 缺页如果需要 I/O 或等待资源,任务就可能睡下,等条件满足后再继续执行。 |
| 系统调用与用户空间边界 | 用户程序虽然只看到一次普通访问,但真正失败或补页发生在内核接手之后。 |
| signals 与进程通知 | 非法访问并不是安静失败,很多时候会沿着异常结果变成进程可见的信号后果。 |
读完这页后,你应该能回答
- 为什么 page fault 常常不是错误,而是虚拟内存按需工作的一部分?
- 为什么同样叫“缺页”,背后却可能是装入文件页、分配匿名页、触发写时复制或直接判错?
- 为什么一次地址访问问题常常会同时牵扯内存、I/O、调度和进程结果?
后面适合继续问:page fault 和 COW 最容易混在哪?缺页为什么会让程序第一次访问某些数据时明显变慢?从非法访问到进程崩溃,中间到底发生了哪些角色切换?