
1. 存储管理这一章为什么总在考试里反复出现如果你正在啃操作系统这门课翻到存储管理这一章的时候大概率会有一种感觉概念不算难但一到做题就懵。分页、分段、段页式、虚拟内存、页面置换算法、缺页中断、地址变换……单拎出来每个词都认识合在一起就变成了一团浆糊。我带过几届学生的操作系统辅导也自己刷过大量这套教材配套的习题可以很负责任地说存储管理是整个操作系统课程里考点最密集、题型变化最多、也最容易拉开分数差距的一章。它不像进程管理那样逻辑线清晰也不像文件系统那样偏记忆存储管理是典型的理解不到位就寸步难行的章节。这一章的核心矛盾其实就一句话程序需要的地址空间和物理内存实际能提供的空间之间存在巨大落差操作系统要在两者之间做一层漂亮的映射。所有的分页、分段、虚拟存储、置换算法本质上都是在解决这个落差带来的各种问题。你把这个主线抓住了后面那些零碎的知识点就能串成一条线。这篇内容我打算按照实际做题和理解的顺序把存储管理这一章里最容易考、最容易错、也最需要想明白的地方系统地捋一遍。不管你是正在准备期中期末还是考研复习到这一块或者只是想把操作系统的基础打扎实下面的内容应该都能帮到你。需要提前说明的是存储管理这一章的题目类型非常固定无非就是那么几类地址变换计算、页面置换过程模拟、缺页率计算、分区分配算法推演、段页式地址结构分析。但每一类题目里都埋着坑而且这些坑往往是教材正文里一笔带过、老师上课不一定强调、但考试一定会考的地方。我下面会把这些坑一个一个挖出来。2. 从一道地址变换题说起逻辑地址到底怎么变成物理地址2.1 为什么地址变换是整章的地基存储管理这一章不管你后面学什么地址变换都是绕不开的第一步。很多同学后面学虚拟内存学得云里雾里根本原因就是地址变换这块没吃透。我先用一个最典型的场景把这件事讲清楚。假设有一个分页系统页面大小是 1KB也就是 1024 字节。现在给你一个逻辑地址 2500十进制问你对应的页号和页内偏移分别是多少。这个问题看起来简单但我见过太多人在考场上算错。正确的做法是页号 逻辑地址 / 页面大小 2500 / 1024 2整数除法取商页内偏移 逻辑地址 % 页面大小 2500 % 1024 452。所以逻辑地址 2500 对应的是第 2 号页页内偏移 452。这个计算本身不难但考试里往往不会直接给你十进制让你算。更常见的是给你一个十六进制的逻辑地址比如 0x0A3F页面大小是 4KB。这时候你需要先把十六进制转成二进制来理解。4KB 2 的 12 次方也就是说页内偏移占 12 位。0x0A3F 展开成二进制是 0000 1010 0011 1111低 12 位是页内偏移高 4 位是页号。低 12 位 1010 0011 1111 0xA3F 2623高 4 位 0000 0所以页号是 0偏移是 2623。这里有一个非常关键的技巧当页面大小是 2 的整数次幂时逻辑地址的二进制表示中低位部分就是页内偏移高位部分就是页号。你不需要真的去做除法直接按位切分就行。这个技巧在考试里能帮你省大量时间而且不容易出错。2.2 页表到底存了什么为什么需要多级页表理解了基本的地址变换之后下一个问题就是页表。页表的核心作用就是记录逻辑页号到物理块号的映射关系。在最简单的单级页表里页表就是一个数组下标是页号数组元素是物理块号。地址变换的过程就是用页号去查页表拿到物理块号然后把物理块号和页内偏移拼起来就得到了物理地址。但这里有一个很现实的问题如果逻辑地址空间很大页表本身就会非常大。举个例子一个 32 位的逻辑地址空间页面大小 4KB那么页号占 20 位也就是说有 2 的 20 次方 大约 100 万个页表项。每个页表项假设占 4 字节那页表本身就要占 4MB 的连续内存。这还只是一个进程的页表如果有几十个进程同时运行光页表就吃掉几百兆内存这显然不合理。所以就有了多级页表。多级页表的核心思想是把页号再拆成几段用第一级页表指向第二级页表第二级页表才真正指向物理块。这样做的好处是不需要为整个逻辑地址空间都分配页表项只需要为实际用到的部分分配。一个进程可能只用了很少的地址空间那大部分二级页表根本不需要存在省下了大量内存。我见过很多同学在这里卡住觉得多级页表反而更复杂了为什么要这么设计。你只要记住一个类比单级页表就像一本完整的字典不管你要查的字在不在里面整本字典都得印出来多级页表就像先查目录再翻到具体章节虽然多了一次查找但不需要把整本书都印出来。这个类比虽然不完全精确但能帮你快速理解多级页表的设计动机。2.3 快表TLB为什么有了页表还需要它多级页表解决了空间问题但带来了时间问题。原来查一次页表就能拿到物理块号现在可能要查两三次内存。每次查页表都要访问内存而内存访问的速度和CPU比起来慢得多。如果每次地址变换都要多访问几次内存系统性能会严重下降。快表TLBTranslation Lookaside Buffer就是为了解决这个问题。TLB 本质上是一个小型的、速度极快的硬件缓存里面存放的是最近使用过的页表项。地址变换的时候先查 TLB如果命中TLB Hit直接拿到物理块号不需要访问内存中的页表如果不命中TLB Miss再去查内存中的页表同时把这条映射关系存入 TLB以便下次使用。这里有一个考试常考的细节TLB 命中时有效访问时间是多少TLB 未命中时有效访问时间又是多少。假设访问一次内存需要 100ns查一次 TLB 需要 10nsTLB 命中率是 90%。那么有效访问时间 0.9 × (10 100) 0.1 × (10 100 100) 0.9 × 110 0.1 × 210 99 21 120ns。注意这里 TLB 命中的情况下只需要一次内存访问取数据未命中的情况下需要两次内存访问一次查页表一次取数据。这个计算逻辑一定要理清楚考试里经常在这里设陷阱。3. 页面置换算法考试里最容易翻车的地方3.1 OPT、FIFO、LRU三种算法的本质区别页面置换算法是存储管理这一章考试出现频率最高的考点没有之一。几乎每套卷子都会有一道题让你模拟页面置换过程然后算缺页次数或缺页率。最常考的三种算法是最佳置换算法OPT、先进先出算法FIFO、最近最久未使用算法LRU。OPT 算法的思路是当需要置换一个页面时选择未来最长时间内不会被访问的页面淘汰。这个算法理论上最优但实际中无法实现因为操作系统不可能预知未来的访问序列。考试里考 OPT 主要是让你用它来作为一个基准和其他算法做对比。FIFO 算法最简单谁先进来谁先出去。用一个队列就能实现。但 FIFO 有一个著名的异常现象叫 Belady 异常增加物理块数缺页次数反而可能增加。这个现象在考试里经常考通常会给你一个访问序列让你分别计算分配 3 个物理块和 4 个物理块时的缺页次数然后你会发现 4 个物理块时缺页次数反而更多。LRU 算法的思路是选择最近最长时间没有被访问的页面淘汰。它基于程序局部性原理实际效果通常接近 OPT。考试里模拟 LRU 的时候你需要维护每个页面最近一次被访问的时间每次置换时选择时间最早的那个。3.2 手把手模拟一次页面置换过程我拿一个具体的例子来演示。假设物理块数为 3页面访问序列是7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1。这个序列是经典教材里常用的例子我带你分别用三种算法走一遍。先看 FIFO。初始三个物理块为空。访问 7缺页装入队列 [7]。访问 0缺页装入队列 [7, 0]。访问 1缺页装入队列 [7, 0, 1]。访问 2缺页淘汰队首 7队列 [0, 1, 2]。访问 0命中。访问 3缺页淘汰队首 0队列 [1, 2, 3]。访问 0缺页淘汰队首 1队列 [2, 3, 0]。访问 4缺页淘汰队首 2队列 [3, 0, 4]。访问 2缺页淘汰队首 3队列 [0, 4, 2]。访问 3缺页淘汰队首 0队列 [4, 2, 3]。访问 0缺页淘汰队首 4队列 [2, 3, 0]。访问 3命中。访问 2命中。访问 1缺页淘汰队首 2队列 [3, 0, 1]。访问 2缺页淘汰队首 3队列 [0, 1, 2]。访问 0命中。访问 1命中。访问 7缺页淘汰队首 0队列 [1, 2, 7]。访问 0缺页淘汰队首 1队列 [2, 7, 0]。访问 1缺页淘汰队首 2队列 [7, 0, 1]。总共缺页 15 次。再看 LRU。同样的序列物理块数 3。访问 7缺页装入 [7]。访问 0缺页装入 [7, 0]。访问 1缺页装入 [7, 0, 1]。访问 2缺页淘汰最久未使用的 7装入 [0, 1, 2]。访问 0命中0 变为最近使用 [1, 2, 0]。访问 3缺页淘汰最久未使用的 1装入 [2, 0, 3]。访问 0命中0 变为最近使用 [2, 3, 0]。访问 4缺页淘汰最久未使用的 2装入 [3, 0, 4]。访问 2缺页淘汰最久未使用的 3装入 [0, 4, 2]。访问 3缺页淘汰最久未使用的 0装入 [4, 2, 3]。访问 0缺页淘汰最久未使用的 4装入 [2, 3, 0]。访问 3命中3 变为最近使用 [2, 0, 3]。访问 2命中2 变为最近使用 [0, 3, 2]。访问 1缺页淘汰最久未使用的 0装入 [3, 2, 1]。访问 2命中2 变为最近使用 [3, 1, 2]。访问 0缺页淘汰最久未使用的 3装入 [1, 2, 0]。访问 1命中1 变为最近使用 [2, 0, 1]。访问 7缺页淘汰最久未使用的 2装入 [0, 1, 7]。访问 0命中0 变为最近使用 [1, 7, 0]。访问 1命中1 变为最近使用 [7, 0, 1]。总共缺页 12 次。OPT 算法我这里就不逐步展开了它的缺页次数是 9 次是最少的。你可以自己试着推一遍关键是在每次需要置换的时候往后看整个剩余的访问序列选择最晚才会被访问的那个页面淘汰。3.3 模拟页面置换时最容易犯的三个错误我带学生做题的时候发现大家在模拟页面置换时反复犯三类错误这里单独拎出来说。第一类错误是忘记更新访问状态。用 LRU 的时候每次命中一个页面这个页面就变成了最近使用的它的淘汰优先级要降到最低。很多同学命中之后就什么都不做导致后面置换的时候选错了淘汰对象。这个错误非常隐蔽因为前面几步可能看不出问题到后面才暴露出来。第二类错误是FIFO 队列维护错误。FIFO 的队列是在页面装入时入队淘汰时出队。但注意命中不会改变队列顺序。有些同学一看到命中就把页面移到队尾那就变成 LRU 了。这个区分一定要清楚。第三类错误是初始状态处理不当。物理块一开始是空的前几次访问一定缺页而且不需要淘汰任何页面直接装入空块就行。有些同学一上来就开始淘汰那肯定全错了。做页面置换模拟题的时候我强烈建议你在草稿纸上画一个表格每一列是一次访问每一行是一个物理块把每次访问后的状态都写清楚。不要偷懒在脑子里算这类题目步骤多一步错步步错。4. 分区分配与碎片问题那些容易被忽略的细节4.1 首次适应、最佳适应、最坏适应的真实差异连续分配管理方式里的分区分配算法是存储管理这一章另一个高频考点。虽然现在实际系统里连续分配已经很少用了但考试里还是经常考。三种基本算法是首次适应First Fit、最佳适应Best Fit、最坏适应Worst Fit。首次适应从内存低地址开始扫描找到第一个足够大的空闲分区就分配。这个算法简单快速但会导致低地址部分被反复切割留下很多小碎片。最佳适应扫描整个空闲分区列表找到能满足需求且最小的那个分区来分配。听起来很美好但实际上会产生大量难以利用的极小碎片因为每次分配都把分区切得刚刚好剩下的部分往往太小了没法再用。最坏适应每次都选最大的空闲分区来分配。这样剩下的部分还比较大可能还能继续用。但缺点是大的空闲分区很快就被消耗完了后面来一个大作业就没有合适的空间了。我个人的经验是考试里最喜欢考的是最佳适应因为它最反直觉——名字叫最佳实际效果往往最差。题目通常会给你一个初始空闲分区列表和一系列分配请求让你分别用三种算法模拟然后比较剩余空闲分区的情况。你只要按部就班地模拟注意每次分配后要更新空闲分区列表就不会出错。4.2 内部碎片和外部碎片的区分碎片问题是存储管理里另一个容易混淆的点。内部碎片是指分配给进程的分区内部没有被用到的部分。比如一个进程需要 7KB 内存系统分配了一个 8KB 的分区那 1KB 就是内部碎片。内部碎片在固定分区分配和分页系统中都存在。外部碎片是指空闲分区之间太小而无法满足任何分配请求的那些碎片。比如内存中有三个空闲分区大小分别是 2KB、3KB、4KB现在来了一个需要 5KB 的进程虽然总空闲空间有 9KB但没有一个单独的分区能满足这就是外部碎片。外部碎片主要出现在动态分区分配和分段系统中。这里有一个考试常考的对比分页系统产生内部碎片不产生外部碎片分段系统产生外部碎片不产生内部碎片。为什么因为分页是固定大小的最后一页可能装不满所以有内部碎片但页与页之间是紧密排列的不会产生外部碎片。分段是可变大小的段与段之间可能留下无法利用的小空隙所以有外部碎片但段内是按需分配的不会浪费所以没有内部碎片。这个逻辑一定要想清楚不要死记硬背。4.3 紧凑技术为什么不能解决所有碎片问题紧凑Compaction是一种解决外部碎片的技术做法是把所有已分配的分区移到内存的一端把空闲分区合并到另一端。这样就能把分散的小空闲分区合并成一个大空闲分区。但紧凑有几个限制。首先紧凑需要移动大量数据非常耗时。其次紧凑需要重定位寄存器的支持因为进程在内存中的位置变了所有相关的地址都要调整。最后紧凑并不是随时都能做的通常只在某个进程结束时才做一次。考试里如果考到紧凑通常会问你紧凑之后空闲分区的情况或者问你紧凑的开销。你只要记住紧凑能消除外部碎片但不能消除内部碎片紧凑是有代价的不是免费的。5. 虚拟内存从原理到考试题型的完整拆解5.1 虚拟内存到底解决了什么问题虚拟内存是存储管理这一章的集大成者也是考试的重点。它的核心思想是程序不需要全部装入内存就能运行只需要装入当前需要的部分其余部分留在磁盘上需要的时候再调入。这样做的好处是程序可以比物理内存大多个程序可以更高效地共享内存程序的装入速度也更快。虚拟内存的实现建立在两个基础上一是分页或分段机制提供了地址变换的能力二是页面置换算法提供了在内存不足时腾出空间的能力。缺页中断是虚拟内存的核心机制当程序访问的页面不在内存中时硬件产生一个中断操作系统负责把缺失的页面从磁盘调入内存。这里有一个非常重要的概念区分缺页中断和普通中断不一样。普通中断是在一条指令执行完毕后检查是否有中断请求而缺页中断是在一条指令执行过程中产生的而且一条指令可能产生多次缺页中断。比如一条指令的操作数跨越了两个页面而这两个页面都不在内存中那就会产生两次缺页中断。这个细节考试里考过很多人没注意。5.2 缺页率的计算与影响因素缺页率是衡量虚拟内存性能的核心指标。缺页率 缺页次数 / 总访问次数。影响缺页率的因素有很多物理块数越多缺页率越低页面大小越大缺页率越低但页面太大又会浪费内存程序的空间局部性和时间局部性越好缺页率越低置换算法越好缺页率越低。考试里经常给你一个访问序列和物理块数让你算缺页率。这个计算本身不难但要注意一个细节缺页率通常用百分比表示而且有时候题目会要求你分别计算不同物理块数下的缺页率然后分析趋势。比如物理块数从 3 增加到 4缺页率反而上升了这就是 Belady 异常。你要能识别出这种情况并解释原因。5.3 页面大小怎么选一个没有标准答案的权衡页面大小的选择是一个典型的权衡问题。页面太小页表会很大而且缺页中断的次数会增加因为每次调入的数据量少页面太大内部碎片会增加而且一次调入的数据可能很多都用不到浪费磁盘I/O。实际系统中页面大小通常在 4KB 到 8KB 之间。这个选择是综合考虑了页表大小、内存碎片、磁盘I/O效率等多个因素的结果。考试里如果考到这个问题通常会让你分析不同页面大小的优缺点你只要把上面这几个维度说清楚就行。6. 段页式管理集大成者的地址变换过程段页式管理是存储管理这一章的最后一个重点也是地址变换最复杂的一种。它的基本思路是先把程序按逻辑结构分成段每个段再分成页。这样既保留了分段在逻辑上的清晰性又保留了分页在内存管理上的高效性。段页式的地址结构是段号 页号 页内偏移。地址变换的过程是先用段号查段表拿到该段的页表起始地址然后用页号查页表拿到物理块号最后把物理块号和页内偏移拼起来得到物理地址。这个过程需要访问三次内存一次查段表一次查页表一次取数据。如果加上 TLB可以加速这个过程。考试里考段页式通常就是让你写出地址变换的完整过程或者计算有效访问时间。你只要把三次内存访问的逻辑理清楚注意 TLB 命中时可以减少访问次数就能做对。我个人的建议是学段页式的时候不要死记步骤而是画一个图逻辑地址被切成三段段号指向段表段表项指向页表页表项指向物理块物理块加上偏移就是物理地址。把这个图记在脑子里不管题目怎么变你都能推导出来。7. 几个我踩过的坑和总结出来的做题习惯存储管理这一章我前前后后学了好几遍每次都有新的体会。下面这几个坑是我自己踩过的也是我看到很多同学反复踩的分享出来希望能帮你少走弯路。第一个坑是把逻辑地址和物理地址搞混。逻辑地址是程序看到的地址从 0 开始编址物理地址是内存单元的实际地址。地址变换就是把逻辑地址转成物理地址。这个区分在简单题里不会出错但在复杂题里比如多级页表加 TLB 的题目里很容易搞混。我的习惯是每次做题都在草稿纸上标明逻辑和物理强迫自己区分。第二个坑是忽略题目中的单位。页面大小是 1KB 还是 1K 字节逻辑地址是十进制还是十六进制物理块号是从 0 开始还是从 1 开始这些细节看起来不起眼但错一个就全错了。我现在做题的习惯是先把题目中的所有数值和单位圈出来确认清楚再动手。第三个坑是页面置换模拟时跳步。有些同学觉得前面几步很简单就跳着写结果中间漏了一步后面全错。我的建议是老老实实一步一步写每一步都写清楚当前内存中的页面和它们的访问状态。慢就是快这句话在做这类题的时候特别对。第四个坑是不理解为什么要有快表。很多同学把快表当成一个额外的知识点去背而没有理解它是为了解决地址变换速度问题而引入的。你只要记住多级页表增加了地址变换的时间开销快表就是用来弥补这个开销的。理解了这一层快表相关的计算题就都顺了。最后一个建议存储管理这一章的题目光看是看不会的必须动手做。我见过太多同学看书的时候觉得都懂一做题就错。你至少要把教材上的例题和课后习题完整地做一遍尤其是页面置换和地址变换这两类题做少了根本形成不了肌肉记忆。做完之后还要总结看看自己错在哪是概念不清还是计算粗心然后有针对性地补。这一章一旦打通了后面学文件系统和I/O管理都会轻松很多因为很多思想是相通的。