公司动态

内存扩充技术全解析:覆盖、交换、虚拟内存对比与缺页中断完整流程

📅 2026/7/23 15:13:42
内存扩充技术全解析:覆盖、交换、虚拟内存对比与缺页中断完整流程
内存扩充技术全解析覆盖、交换、虚拟内存对比与缺页中断完整流程本文从一道让我当年栽了跟头的 408 真题讲起把覆盖、交换、虚拟内存三种技术的底层逻辑、地址变换流程、缺页中断处理全部拆开讲透。不是背定义而是把为什么这样设计的因果链讲清楚。文末附四套考场答题模板和完整计算例题。目录文章目录内存扩充技术全解析覆盖、交换、虚拟内存对比与缺页中断完整流程目录一、从一道真题说起二、根本矛盾程序比内存大三、覆盖程序员的手动挡3.1 怎么工作的3.2 覆盖树3.3 为什么被淘汰四、交换OS 的半自动挡4.1 怎么工作的4.2 几个容易忽略的点4.3 根本局限五、虚拟内存全自动挡5.1 理论根基局部性原理5.2 三大特征5.3 请求分页的页表5.4 外存怎么组织的六、三者对比一张表搞定七、地址变换带快表的请求分页7.1 硬件基础7.2 地址结构7.3 完整流程7.4 内存访问次数高频考点7.5 EAT 公式408 必考计算八、缺页中断完整处理流程8.1 性质选择题最爱考8.2 处理流程8.3 几个值得深挖的点8.4 用 GDB 观察缺页中断九、易错点汇总十、例题实战例题 1EAT 计算例题 2地址变换例题 3选择题十一、答题模板考场直接抄模板 A三者区别模板 B地址变换流程模板 C缺页中断处理模板 D缺页中断定义与特点十二、延伸阅读写在最后一、从一道真题说起2019 年 408 统考有一道选择题大意是问以下关于缺页中断的叙述哪个正确。四个选项分别涉及中断类型、产生次数、恢复方式。我的朋友当年做这道题的时候在 A 和 D 之间犹豫了将近两分钟——因为脑子里一直有个模糊的印象“缺页中断涉及磁盘 I/O那它算不算外中断”最后蒙对了。但那种似懂非懂的感觉让我的朋友很不舒服。后来我的朋友把整个请求分页的地址变换流程从头到尾推了三遍又翻了 Linux 内核do_page_fault的源码才真正把这块吃透。这篇文章就是那次推三遍的产物。二、根本矛盾程序比内存大所有内存扩充技术的出发点都是同一个矛盾程序需要的地址空间 物理内存容量。解决思路也很朴素装不下全部就只装一部分剩下的放磁盘用的时候再拿。三种技术的区别在于三个问题切多细谁来管什么时候拿方案切多细谁来管什么时候拿覆盖程序段模块程序员自己程序员写代码控制交换整个进程OS中级调度进程阻塞/内存紧张时虚拟内存页4KBOS MMU 硬件访问时缺页才拿三代演进一代比一代自动化一代比一代粒度细。三、覆盖程序员的手动挡3.1 怎么工作的把程序按功能拆成模块常用的常驻内存不常用的需要时调入用完覆盖掉。拿一个编译器举例// 伪代码覆盖结构的编译器// 主程序常驻内存50KBintmain(){// 覆盖区70KB依次装入不同模块overlay_load(lexer);// 词法分析 40KBdo_lexical_analysis();overlay_load(parser);// 语法分析 60KB覆盖掉 lexerdo_syntax_analysis();overlay_load(semantic);// 语义分析 50KB覆盖掉 parserdo_semantic_analysis();overlay_load(codegen);// 代码生成 70KB覆盖掉 semanticdo_code_generation();}全部装入需要 270KB覆盖后只需 50 max(40,60,50,70) 120KB。3.2 覆盖树调用关系复杂时用覆盖树描述。核心规则兄弟节点互斥共享覆盖区父子节点可共存。A常驻20KB / \ B D (30KB) (40KB) | | C E (50KB) (25KB)所需内存 20 max(30,50) max(40,25) 110KB。3.3 为什么被淘汰三个字太累了。程序员得手动分析调用图、手动写 overlay 调用、程序一改就得重新设计。1960 年代 IBM OS/360 和 PDP-11 的 RT-11 广泛使用这套机制链接器甚至有专门的覆盖段语法。到了 1980 年代虚拟内存普及覆盖就退出了主流——因为同样的事情 OS 和硬件能自动做而且做得更好。四、交换OS 的半自动挡4.1 怎么工作的内存紧张时把暂时不跑的进程整体换出到磁盘交换区腾出空间给别的进程。时刻 T1内存满 ┌────────┬────────┬────────┐ │ 进程 A │ 进程 B │ 进程 C │ └────────┴────────┴────────┘ 时刻 T2B 阻塞换出D 换入 ┌────────┬────────┬────────┐ │ 进程 A │ 进程 D │ 进程 C │ └────────┴────────┴────────┘ ↕ 磁盘交换区进程 B4.2 几个容易忽略的点交换区不是普通文件。它是磁盘上专门划分的连续区域以原始块设备方式读写绕过了文件系统的元数据开销。Linux 里可以是独立的 swap 分区也可以是文件系统上的 swap 文件swapon命令挂载。前者 I/O 效率略高后者灵活可以动态fallocate扩容。触发者是中级调度。不是高级调度作业调度也不是低级调度CPU 调度是介于两者之间的中级调度Medium-Term Scheduling。进程被换出后状态变为挂起态SuspendPCB 仍留在内存。换出时要保存完整上下文。寄存器、PC、栈指针全部存到 PCB。换入时原样恢复进程从断点继续跑。4.3 根本局限交换没有扩充内存。它只是在进程之间倒腾空间每个进程仍然必须完整装入内存才能运行。一个 200MB 的进程如果物理内存只有 128MB交换也救不了你。而且粒度太粗——换出一个进程要搬运整个地址空间几十 MB 的磁盘 I/O毫秒到秒级开销。CPU 一次访存才 100ns差了好几个数量级。五、虚拟内存全自动挡5.1 理论根基局部性原理程序在任意时刻真正活跃的数据只是很小一部分。这个观察被 Denning1968形式化为工作集模型进程在时间窗口 Δ 内访问的页面集合 W(t, Δ)。只要工作集装得下进程就能高效运行装不下就频繁缺页系统陷入抖动Thrashing。我在实验室里亲眼见过抖动一台 4GB 内存的机器跑了太多虚拟机vmstat 1看到 si/soswap in/out持续几百 KB/sCPU 的waiowait飙到 90% 以上整个系统基本卡死。杀掉几个进程后立刻恢复。这就是工作集装不下的典型表现。5.2 三大特征多次性作业分多次调入不必一次性全装对换性运行中页面可以换入换出虚拟性逻辑地址空间远大于物理内存32 位系统4GB 虚拟空间 vs 可能只有 512MB 物理内存5.3 请求分页的页表比基本分页多了几个关键字段字段干什么用的有效位 P1在内存0不在触发缺页修改位 D1被写过换出时要写回磁盘访问位 R1被访问过给 CLOCK 算法用的保护位读/写/执行权限外存地址页面在磁盘上的位置这里有个容易混的点保护位违反和缺页是两回事。页面在内存P1但你写了一个只读页触发的是保护异常Linux 里表现为 SIGSEGV不是缺页中断。只有 P0 才是缺页。另外一个细节P0 不一定意味着页面在磁盘上。可能是malloc后还没首次访问demand zeroing页面根本还没分配可能是文件映射页还没读入也可能是正在被别的进程调入共享页场景。OS 的缺页处理程序会区分这些情况。5.4 外存怎么组织的Linux 里进程的页面分两种来源文件映射页File-backed代码段.text、只读数据.rodata直接映射到可执行文件。淘汰时直接丢弃需要时从文件重读不用写回。匿名页Anonymous堆、栈、mmap(MAP_ANONYMOUS)分配的内存。没有对应文件脏页淘汰时必须写回 swap 分区。这就是为什么 Linux 即使内存够用也建议配 swap——不是为了扩充内存而是为了有地方放匿名脏页。你可以用cat /proc/meminfo | grep Swap看当前 swap 使用情况。六、三者对比一张表搞定维度覆盖交换虚拟内存作用范围同一程序内部不同进程之间单个进程内部管理者程序员OS中级调度OS MMU粒度段可变整个进程页4KB透明不透明透明透明扩充内存是有限否是大幅理论基础调用图多道程序设计局部性原理硬件要求无磁盘页表TLB中断机构进程状态不涉及→ 挂起态→ 阻塞态考场速记覆盖管段程序员手动交换管进程OS 自动虚拟内存管页OS硬件自动。七、地址变换带快表的请求分页7.1 硬件基础三个关键角色PTBR页表基址寄存器存当前进程页表的物理起始地址。进程切换时由 OS 设置。PTLR页表长度寄存器存页表项个数用于越界检查。TLB快表CPU 内部的高速缓存存最近用过的页表项。命中率通常 95%~99%。TLB 的存在是为了解决一个性能问题没有 TLB 时每次访存都要先查内存中的页表1 次访存再取数据又 1 次两次访存太慢。有了 TLB大部分情况直接命中省掉查页表那次。进程切换时 TLB 怎么处理早期是全刷新代价大现代 CPU 给每个 TLB 条目加一个进程标识符x86 叫 PCIDARM 叫 ASID切换时不用刷匹配标识符就行。7.2 地址结构逻辑地址[ 页号 P高位 | 页内偏移 W低位] 物理地址[ 块号 f高位 | 页内偏移 W低位不变] 物理地址 f × 页面大小 W页面大小 4KB 2¹²所以偏移占低 12 位。32 位地址空间里页号占高 20 位最多 1M 页。7.3 完整流程我画了一张流程图建议对着这个图把流程走三遍走到能默写出来为止CPU 发出逻辑地址 │ ▼ 提取页号 P、偏移 WMMU 硬件 │ ▼ P ≥ PTLR ──── 是 ──→ 越界中断终止 │ 否 ▼ 查 TLB ──── 命中 ──→ 获得块号 f ──────────────────┐ │ │ 未命中 │ ▼ │ 查内存页表PTBR P×项大小 │ │ │ ▼ │ 有效位 1 │ │ │ │ 是 否 │ ▼ ▼ │ 获得 f 缺页中断 │ 更新TLB 见第八节 │ │ 处理完重新执行 ──→ 回到查 TLB │ │ │ └────────────────────────────────────────────────┘ │ ▼ 硬件更新访问位1 若写操作修改位1 │ ▼ 物理地址 f × 页大小 W CPU 访问物理内存7.4 内存访问次数高频考点场景访存次数解释TLB 命中1 次取数据。TLB 在 CPU 内部不算访存TLB 未命中页在内存2 次1 次查页表 1 次取数据缺页2 次 磁盘 I/O查页表发现缺页 → 磁盘读 → 重新执行这里有个坑很多人以为 TLB 命中就是 0 次访存。不是。TLB 只是省了查页表那次取数据仍然要访问内存。除非数据恰好在 L1/L2 Cache 里但那是 Cache 的事跟 TLB 无关。补充一点现代系统用多级页表x86-64 是 4 级PGD→PUD→PMD→PTE。没有 TLB 的话理论上要 4 次访存查页表 1 次取数据 5 次。所以 TLB 在多级页表系统里更加不可或缺——命中时仍然只要 1 次跟页表几级没关系。7.5 EAT 公式408 必考计算设ma 访存时间如 100nsε TLB 命中率p 缺页率t_PF 缺页处理时间含磁盘 I/O约 8ms。E A T ε ⋅ m a ( 1 − ε ) ⋅ [ ( 1 − p ) ⋅ 2 m a p ⋅ t P F ] EAT \varepsilon \cdot ma (1-\varepsilon) \cdot [(1-p) \cdot 2ma p \cdot t_{PF}]EATε⋅ma(1−ε)⋅[(1−p)⋅2map⋅tPF​]这个公式的前提TLB 命中就一定不缺页。为什么因为页面被换出时 OS 会 invalidate 对应 TLB 条目所以 TLB 里存在的条目一定对应 P1 的页表项。分支逻辑概率 εTLB 命中1 次访存耗时 ma概率 (1-ε)TLB 未命中先查页表1 次 ma然后概率 (1-p)页在内存再取数据1 次 ma共 2ma概率 p缺页耗时 t_PF缺页率对性能的影响有多夸张看这个表ε0.98, ma100ns, t_PF8ms缺页率 pEAT衰减体感0102 ns1×理想10⁻⁵~104 ns1.02×无感10⁻⁴~118 ns1.16×还行10⁻³~260 ns2.5×有点慢10⁻²~1,700 ns17×明显卡10⁻¹~16,100 ns158×废了每增加一个数量级性能恶化约一个数量级。这就是为什么工作集必须装得下——装不下就抖动CPU 利用率趋近于零。八、缺页中断完整处理流程8.1 性质选择题最爱考先说结论再解释为什么缺页中断是内中断同步异常Fault 类不是外中断。为什么因为它是 MMU 在指令执行期间检测到有效位0 时触发的是 CPU 内部事件。虽然后续处理会涉及磁盘 I/O那是外中断但缺页中断本身的触发机制是内部的。打个比方你打开冰箱发现没菜了内中断你自己发现的然后打电话叫外卖后续处理外卖路上堵车外部事件。但发现没菜这个触发点是你内部的不是外面有人敲门告诉你没菜了。其他性质一条指令可产生多次缺页x86 的REP MOVSB每次迭代都可能缺页跨页指令的源和目的在不同缺页页面处理完后重新执行原指令不是执行下一条因为指令还没完成架构状态没更新进程从运行态→阻塞态等磁盘 I/OCPU 去跑别的进程8.2 处理流程以 Linux x86-64 为例从硬件触发到恢复执行的完整路径硬件触发 MMU 检测 P0 → 缺页地址写入 CR2 寄存器 → PC 压入内核栈 → CPU 切换到 Ring 0跳转到 IDT 中 page fault 入口 OS 处理do_page_fault → handle_mm_fault ① 保存 CPU 现场通用寄存器 ② 从 CR2 读取缺页地址确定页号 合法性检查 地址不在进程 VMA 范围内 → 发 SIGSEGV进程可能被杀 权限不符写只读页→ 保护异常处理COW 等 合法缺页 → 继续 ③ 有空闲页框 有 → 直接用 无 → 置换算法选淘汰页 修改位1脏页→ 写回磁盘一次写 I/O 修改位0干净→ 直接丢弃省一次 I/O ④ 进程 → 阻塞态发起 DMA 读盘 CPU 转去调度其他进程 ⑤ 磁盘 I/O 完成 → 硬件中断 → 进程 → 就绪态 ⑥ 更新页表P1填入块号D0 ⑦ 恢复现场重新执行原指令 → 查 TLB未命中→ 查页表P1→ 硬件填充 TLB → 访问内存8.3 几个值得深挖的点为什么脏页要写回干净页不用干净页的磁盘副本还是最新的没被改过直接丢弃就行需要时从原处重读。脏页的磁盘副本已经过时了不写回数据就永久丢失。一次磁盘写 I/O 大约 8~10ms能省就省。这也是为什么 CLOCK 改进算法二次机会同时看访问位和修改位——优先淘汰最近没访问且没修改的页面。为什么是重新执行引起缺页的那条指令还没完成。它想读一个内存数据数据所在页不在内存指令在取数阶段被打断。此时目标寄存器没写入、标志位没修改架构状态跟这条指令执行前一模一样。数据调进来后从头再执行这条指令就行了。现代 CPU 通过精确异常机制保证这一点异常发生时流水线中该指令之后的所有指令全部冲刷架构状态回退。所以重新执行是安全的。TLB 什么时候更新缺页处理程序OS 代码更新的是内存中的页表不是 TLB。TLB 由硬件在下次地址变换时自动填充。所以重新执行指令时还会经历一次 TLB Miss → 查页表 → 填充 TLB 的过程。此后再访问同一页TLB 就命中了。多进程缺同一页怎么办比如共享库的代码页多个进程同时缺同一页。OS 通常只发起一次磁盘 I/O其他进程也阻塞等同一个 I/O 完成。避免重复读盘。Linux 里这叫 page cache 共享。8.4 用 GDB 观察缺页中断如果你想在真实系统上观察缺页行为可以写一个简单的程序#includestdio.h#includestdlib.h#includestring.hintmain(){// malloc 返回时页面并未真正分配demand zeroing// 首次访问时才触发缺页中断OS 分配物理页框并清零char*pmalloc(4096*10);// 申请 10 页但此时 0 次缺页printf(malloc done, now touching pages...\n);// 每次访问新的一页触发一次缺页中断for(inti0;i10;i){p[i*4096]x;// 触发第 i 页的缺页中断}printf(done. Check /proc/self/statm for RSS change.\n);// 查看当前进程的内存使用FILE*ffopen(/proc/self/statm,r);intsize,resident;fscanf(f,%d %d,size,resident);printf(Virtual pages: %d, Resident pages: %d\n,size,resident);fclose(f);free(p);return0;}编译运行后用strace -e tracemmap,brk ./a.out可以看到系统调用用perf stat -e page-faults ./a.out可以直接统计缺页次数。在我的机器上这个程序大约产生 12~15 次 minor page fault包括 libc 初始化带来的。九、易错点汇总把当年坑过我的、也坑过不少考生的点列在这里。不是简单的对错判断而是把为什么会搞混讲清楚。TLB 命中 0 次访存不是。TLB 命中 1 次访存取数据。TLB 省的是查页表那次不是所有访存。这个错误在选择题里出现频率极高因为直觉上命中听起来像是什么都不用做了。缺页中断是外中断不是。触发点是 MMU 检测有效位CPU 内部不是磁盘控制器发信号外部。磁盘 I/O 是处理步骤不改变中断类型。先查缺页还是先查越界先越界。P ≥ PTLR 说明地址本身非法此时去查页表可能访问非法物理地址总线错误。必须先排除非法情况。交换也能扩充内存不能。交换只是进程间调度空间单个进程仍须完整装入。它是调度不是扩充。淘汰页面都要写回不是。只有 D1脏页才写回。D0 直接丢弃。进程缺页时 CPU 空转不是。进程阻塞CPU 去跑别的进程。I/O 完成后中断唤醒。这是多道程序设计的基本操作。十、例题实战例题 1EAT 计算ma100nst_PF8msε0.98。1p0.0001 时 EAT EAT 0.98×100 0.02×[(1-0.0001)×200 0.0001×8000000] 98 0.02×[199.98 800] 98 0.02×999.98 ≈ 98 20 118 ns2EAT ≤ 120ns 时 p 最大多少98 0.02×[200 8000000p] ≤ 120 p极小时忽略 p×200 4 160000p ≤ 22 160000p ≤ 18 p ≤ 1.125×10⁻⁴严格解保留所有项159996p ≤ 18p ≤ 1.12503×10⁻⁴。结果一样考场用近似就行。直觉验证万分之一意味着每 10000 次访存缺页一次一次缺页等 8ms 80000 次访存的时间所以平均多花 80000/10000 8% 的时间。102×1.08 ≈ 110跟 118 量级吻合差异来自 TLB 未命中分支的加权。例题 2地址变换页面 4KB地址空间 64KB8 个页框已满。页号块号PDR031011—0——2511037101访问逻辑地址0x1A3FTLB 未命中。拆地址4KB2¹²低 12 位是偏移。0x1A3F→ 页号 P1偏移 W0xA3F2623。越界检查PTLR64K/4K16P116通过。查 TLB未命中。查页表页号 1P0缺页。缺页处理无空闲页框CLOCK 算法选中页号 2R0, D1D1脏页写回磁盘页号 1 从磁盘读入块号 5更新页表页号 1P1块号5D0重新执行查 TLB 未命中 → 查页表 P1f5 → 硬件填充 TLB物理地址5×40962623 23103 0x5A3F例题 3选择题关于缺页中断正确的是A. 属于外中断B. 一条指令最多产生一次C. 处理后执行下一条指令D. 属于内中断一条指令可产生多次答案 D。A 错内中断B 错REP MOVSB、跨页指令C 错重新执行原指令。十一、答题模板考场直接抄模板 A三者区别1作用范围覆盖——同一程序内部交换——不同进程之间虚拟内存——单个进程内部。2实现主体覆盖——程序员手动交换——OS 中级调度虚拟内存——OSMMU 协同。3粒度覆盖——程序段交换——整个进程虚拟内存——页4KB。4是否扩充覆盖和虚拟内存逻辑扩充交换仅调度不扩充。5理论基础覆盖——调用图交换——多道程序设计虚拟内存——局部性原理。6透明度覆盖不透明交换和虚拟内存透明。模板 B地址变换流程1提取页号 P 和偏移 W。2P 与 PTLR 比较P≥PTLR 则越界中断。3查 TLB命中→获得 f转6未命中→查内存页表PTBRP转4。4检查有效位P1→获得 f写入 TLB转6P0→缺页中断处理后重新执行。5缺页处理6更新访问位1写操作则修改位1。7物理地址 f×页大小W。8访问内存。模板 C缺页中断处理1保存现场PC、PSW、寄存器。2从 CR2 确定缺页地址和页号合法性检查。3有空闲页框→直接用无→置换D1 写回D0 丢弃。4进程→阻塞态DMA 读盘CPU 调度其他进程。5I/O 完成→进程→就绪态。6更新页表P1填块号D0。7恢复现场重新执行原指令。模板 D缺页中断定义与特点缺页中断是 CPU 访问的页面不在内存P0时由 MMU 触发的内部异常。特点1内中断非外中断2一条指令可产生多次3处理后重新执行原指令精确异常4硬件检测触发OS 负责调页。十二、延伸阅读汤小丹《计算机操作系统》第四版第 4 章王道《操作系统考研复习指导》内存管理章节Tanenbaum《现代操作系统》第 4 章CSAPP 第 9 章虚拟内存讲得最透的一本Linux 源码arch/x86/mm/fault.cdo_page_fault入口Linux 源码mm/memory.chandle_mm_fault核心逻辑用perf stat -e page-faults,minor-faults,major-faults观察真实系统的缺页行为写在最后这块内容我前后看了大概有四五遍才真正吃透。第一遍看教材觉得好像懂了第二遍做 408 真题发现其实没懂第三遍推地址变换流程把每个分支都走了一遍第四遍翻 Linux 源码把 OS 侧的处理逻辑对上号第五遍给同学讲了一遍讲的过程中发现自己还有几个点含糊。如果你现在处于好像懂了的阶段我的建议是拿一张白纸把地址变换流程从头画到尾每个判断节点都标清楚。画不出来的地方就是你没懂的地方。画三遍基本就稳了。觉得有用的话点个赞收藏有问题评论区聊。标签操作系统内存管理虚拟内存缺页中断请求分页TLB页面置换408考研EATLinux