缺页中断:内存不够时操作系统怎么办?补页与换页
缺页中断:内存不够时操作系统怎么办?补页与换页
物理内存永远不够用,但操作系统总能让程序以为自己独占一大片内存——秘密就在「缺页」这套随用随补的机制里。
一、背景与痛点
你有没有遇到过这种情况:程序冷启动后第一次点某个功能,界面会「顿」一下;手机切回一个后台很久的 App,要转圈一两秒才恢复;容器集群里某个 Pod 突然内存飙升,被 OOM Killer 干掉。这些看似无关的现象,背后其实是同一件事——缺页(page fault)与页面置换在起作用。
问题的源头很朴素:物理内存是有限的,而所有进程的虚拟地址空间加起来可以远超物理 RAM。程序以为自己拥有一整片连续内存,可真要一个字节一个字节全塞进物理内存,机器早就爆了。操作系统用一个巧妙的机制化解了矛盾:绝大多数页根本不必立刻驻留内存,用到时再补;当物理内存真的不够、又有新页要进来时,就把最不值得留的旧页请出去。前者叫缺页,后者叫页面置换。本文先把「内存不够」这件事的发生过程和缺页的完整流程讲清楚,下一期再专门讲「选谁走」的算法。
二、核心原理
现代系统几乎都采用按需分页(demand paging)。程序启动时,内核并不把整个可执行文件、堆、栈一股脑搬进内存,而是先建立页表映射,把存在位(present)清成 000,表示「这一页现在不在内存里」。当 CPU 首次访问某个虚拟页,MMU 查页表发现存在位为 000,无法译出物理地址,于是触发缺页异常,陷入内核。内核接手后分配一个空闲物理帧,把该页内容(来自可执行文件、全零的匿名页、或换出到磁盘的页)读进来,填好页表项的存在位与物理帧号,再让出错指令重新执行——这一次翻译就成功了。
值得强调的是:page fault 是个中性术语,不是崩溃级错误。按成因它分好几类。次要缺页(minor/soft fault)是指页不在内存但内容就在别处(例如共享库已被别的进程加载、或页还在页缓存里),内核只需建立映射,不用读盘,代价很小。主要缺页(major/hard fault)是指页既不在内存也不在缓存,必须真的从磁盘(文件或交换区)读进来,代价是毫秒级,是性能尖刺的主要来源。保护缺页则是权限不匹配(写只读页、执行不可执行页),内核可能做写时复制或直接发 SIGSEGV。真异常是访问了根本没映射的地址(空指针、越界)。
那么「内存真的不够」是怎么发生的?内核维护一个空闲帧链表。启动初期内存宽裕,缺页就从链表拿一帧填上即可。但当链表被耗尽——所有物理帧都被占满,而又有新缺页要进场——矛盾就正面爆发:没有空位留给新页。此时内核必须从已在内存的页里挑一个腾出来。如果那页是干净的(脏位 D=0D=0D=0),直接丢弃即可;如果是脏的(D=1D=1D=1),得先把内容写回磁盘才能腾帧,这一步带来一次写盘开销。腾出的帧分配给新缺页的进程,流程继续。这个「腾旧页给新页」的动作就是页面置换,被挑中请走的页叫 victim。注意置换发生在缺页处理内部:不是预先腾好,而是「撞到墙了才换」,这正是按需分页随需随换的哲学。
用数学刻画性能,缺页率 ppp 直接决定有效访问时间:
EAT=(1−p) tmem+p tfaultEAT = (1-p)\,t_{mem} + p\,t_{fault}EAT=(1−p)tmem+ptfault
其中 tmemt_{mem}tmem 是命中时的访问时间(纳秒级),tfaultt_{fault}tfault 是一次主要缺页的处理时间(毫秒级),两者相差五六个数量级。所以哪怕 ppp 只有 0.0010.0010.001,EATEATEAT 也会被 tfaultt_{fault}tfault 拉高好几倍。更危险的是抖动(thrashing):当所有进程的工作集总和 ∑iWi\sum_i W_i∑iWi 超过可用物理内存 MMM,即
∑iWi>M\sum_i W_i > Mi∑Wi>M
系统就会陷入「一直在换页、没空干活」的恶性循环,吞吐量断崖式下跌。
三、代码实战
下面用纯标准库仿真一个「随需随换」的内存系统。它维护一个有限的物理帧池和一个空闲链表,给定一条访问虚拟页的序列:命中就直接访问;未命中且有空闲帧则记为次要缺页;空闲耗尽则触发置换并记一次换盘(主要缺页)。为了突出「内存不够」的临界点,这里先用最简单的占位置换策略,下一期再换成真正的 FIFO/LRU 算法。
import random
random.seed(3)
N_VPAGES = 200
working = list(range(40)) # 工作集:40 个常用虚拟页
seq = []
for _ in range(2000):
if random.random() < 0.85:
seq.append(random.choice(working)) # 85% 落回工作集
else:
seq.append(random.randint(0, N_VPAGES - 1)) # 15% 冷页
def simulate(frames):
in_mem = set() # 当前驻留内存的页
free = list(range(frames)) # 空闲物理帧池
minor = major = 0
for v in seq:
if v in in_mem: # 命中,直接访问
continue
if free: # 有空闲帧:次要缺页
free.pop()
in_mem.add(v)
minor += 1
else: # 空闲耗尽:置换并换盘,主要缺页
victim = next(iter(in_mem)) # 占位策略,第 033 期换 FIFO/LRU
in_mem.discard(victim)
in_mem.add(v)
major += 1
return minor, major
for f in [10, 20, 30, 40, 60, 80, 120, 200]:
mi, ma = simulate(f)
total = mi + ma
print(f"帧数={f:3d} 次要缺页={mi:4d} 主要缺页={ma:4d} 总缺页率={total/len(seq)*100:.1f}%")
运行结果:
帧数= 10 次要缺页= 10 主要缺页=1923 总缺页率=96.7%
帧数= 20 次要缺页= 20 主要缺页=1875 总缺页率=94.8%
帧数= 30 次要缺页= 30 主要缺页=1793 总缺页率=91.1%
帧数= 40 次要缺页= 40 主要缺页=1719 总缺页率=87.9%
帧数= 60 次要缺页= 60 主要缺页=1517 总缺页率=78.8%
帧数= 80 次要缺页= 80 主要缺页=1276 总缺页率=67.8%
帧数=120 次要缺页= 120 主要缺页= 572 总缺页率=34.6%
帧数=200 次要缺页= 163 主要缺页= 0 总缺页率= 8.2%
可以看到非常清晰的规律:当帧数远小于工作集(40 页)时,访问几乎每次都会撞上置换,总缺页率高达 96.7%,而且几乎全是昂贵的主要缺页;随着帧数增大,主要缺页快速减少,总缺页率一路降到 8.2%。当帧数达到 200(能装下全部页)时,主要缺页直接归零,只剩少量良性次要缺页。关键临界点就落在帧数接近工作集大小附近——这直观解释了「内存够不够」为什么是性能的分水岭:一旦物理帧装不下工作集,系统就会持续换盘,把 ns 级访问拖成 ms 级等待。
四、关键经验/避坑
- 别把 page fault 当故障报警。绝大多数缺页是正常的按需加载路径,只有访问未映射地址、或无法处理的权限冲突才会演变成 SIGSEGV。一见 fault 就报警,只会制造大量假阳性。
- 看指标要把 minor 与 major 拆开。minor 基本无害,major 才说明内存吃紧、在频繁换盘;把两者混为一谈,会把无害的懒加载噪声当成系统故障。
- 「关掉 swap 就更快」是错觉。swap 不是用来日常跑的,而是兜底防 OOM。没有 swap,内存一旦耗尽只能杀进程,可能干掉关键服务;保留一点 swap 反而更稳。
- 置换不只发生在内存 100% 占满那一刻。内核有空闲水位线,会在真正耗尽前就后台回收;而且锁定页、内核页、正进行 IO 的页都不能换出,所以「还有空闲内存却在换出」并不矛盾。
- 区分文件页与匿名页。文件页背后有磁盘文件,脏了写回原文件、干净了直接丢,换出便宜;匿名页(堆/栈/malloc)没有文件做后盾,只能写进 swap,更加金贵。看到「已用内存很高但很流畅」多半是文件页缓存,不必惊慌。
五、完整系列推荐
📚 本文选自《操作系统内核与驱动详解》100 期系统教程(第 032 期:缺页与页面置换),每期配可运行 Python 代码。
完整系列(100 期正文 + 3 篇番外,每期文章+代码)已在 ima 知识号【Kruptos】持续更新:
- 🗂 70+ 技术知识库:操作系统、图神经网络、强化学习、数据库系统、推荐系统、编译原理……几乎覆盖全部软硬件技术栈
- 🧠 8 款 AI 技能:系列生产、知识库管理、CMMI 受管开发、自进化 Agent 等,已在 ima 技能广场上架,即装即用
- ✅ 全部免费订阅,后续更新自动推送
🔍 订阅方式:打开 ima(腾讯智能工作台)→ 搜索「Kruptos」→ 一键订阅;或在 ima 内直接搜索《操作系统内核与驱动详解》。
作者:Kruptos(西电毕业,13 年无线通信/DSP/嵌入式科研,现深耕 AI 与云原生)
原创内容,转载注明出处。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)