页置换算法

缺页而内存又满了,就必须淘汰一页。淘汰谁,决定了缺页率的高低。

本节五个算法,考法分两类:手算缺页次数(计算题)和辨析各算法的性质(选择题)。先给手算方法,再逐个讲。

机制

手算三步法

第一步:画表。 横轴是访问串,纵轴是各个页框。每一列代表一次访问。

第二步:逐列判断三件事,顺序不能乱:

  1. 该页是否已在内存?在 → 命中,不缺页(但可能要更新算法的记录,如 LRU 的顺序、Clock 的访问位)
  2. 不在 → 缺页。内存是否已满?未满 → 直接放入空闲页框
  3. 已满 → 按算法规则选一页淘汰,再放入

第三步:数缺页次数。 注意:最初把页装入空页框的那几次也算缺页(因为确实发生了缺页中断)。

最容易错的地方是第 1 步的”命中也要更新记录”——FIFO 命中时不改变队列顺序,而 LRU 命中时必须更新最近使用顺序。这一条记反了,整道题就全错。

① OPT 最佳置换算法

规则:淘汰未来最长时间内不再被访问的页面;若某页以后再也不会用到,优先淘汰它。

OPT 的缺页率最低,是理论上的最优解。但它需要预知未来的访问序列,实际系统无法实现,只能作为衡量其他算法优劣的标尺。

疑问点:OPT 的手算技巧

手算最佳置换算法时如何快速判断? 自己模拟时是向后看、找最晚使用的那个——这个思路是否正确?

思路完全正确:缺页且内存已满时,只看当前内存中的那几页,比较它们各自”下一次出现的位置”,最靠后的那个(或以后不再出现的)被淘汰。

手算的关键在于不要每次都重新扫描整个访问串。 更稳的做法:

  1. 在访问串上,给每个位置标出该页下一次出现的位置(不再出现的记为 ∞)
  2. 缺页时只比较内存中那几页的标记值
  3. 取最大的那个淘汰

这样每次决策只需比较 3~4 个数,而不是重扫全串。

关联对照:作为算法题时的复杂度优化

“每次都向后看肯定会超时”——这个判断是对的。朴素做法每次淘汰都要向后扫描整个剩余序列,最坏 。

通用优化是预处理”下一次出现位置”:

  1. 从后往前扫一遍访问串,用一个哈希表记录每个页最近一次出现的位置,即可在 内求出每个位置的 nxt[i](该页下一次出现在哪)。
  2. 用一个按 nxt 排序的堆或有序集合维护当前内存中的页。淘汰时直接取堆顶(nxt 最大者)。
  3. 每次访问后更新该页的 nxt 并调整堆。

总复杂度降到 。

这个”预处理下一次出现位置”的技巧在很多题里通用,本质是把”每次向后找”变成”一次性算好,之后 O(1) 查表”。

考试手算时不必这么严格,但理解”要比较的是 nxt 值”能让手算快很多。

② FIFO 先进先出算法

规则:淘汰最早进入内存的页面。用一个队列记录页面调入的先后,每次淘汰队头。

实现最简单,但性能很差——最早调入的页很可能是被频繁访问的核心页(比如主循环所在的页)。

FIFO 是唯一会产生 Belady 异常的算法。

Belady 异常:增加分配的页框数,缺页次数反而增多的反常现象。

这个现象之所以反直觉,是因为我们默认”内存越多越好”。但 FIFO 的淘汰依据是”进来的早晚”,这和”将来还用不用”毫无关系。页框数变化会打乱队列的进出节奏,在特定访问串下就可能变得更糟。

只有 FIFO 会出现 Belady 异常,LRU 和 OPT 都不会——因为它们属于栈算法,具有”页框数增加时,驻留集只增不减”的性质。这是考试的固定考点。

③ LRU 最近最久未使用算法

规则:淘汰最近最久未被访问的页面。

它是 OPT 的”过去镜像”——OPT 向未来看,LRU 向过去看,赌的是时间局部性:最近很久没用的,将来多半也不会马上用。

LRU 是实际效果最接近 OPT 的算法,且不会出现 Belady 异常。

疑问点:LRU 需要维护什么,为何实现代价高

LRU 具体维护的是什么? 22. 导致 LRU 算法实现起来耗费高的原因是( )。 A. 需要硬件的特殊支持 B. 需要特殊的中断处理程序 C. 需要在页表中标明特殊的页类型 D. 需要对所有的页进行排序

操作系统或硬件层面究竟是怎么实现的?

LRU 维护的是”所有驻留页的最近使用顺序”。选择题答案是 A:需要硬件的特殊支持。

代价高的根源在于更新的频率:

每一次访存都必须更新这个顺序记录——不只是缺页时,命中时也要更新。

一个程序每秒执行上亿次访存,如果每次都要陷入内核跑一段软件去维护顺序,开销会远远超过程序本身的计算量。所以这件事只能交给硬件顺带完成,这正是选 A 的理由。

两种典型的硬件实现:

寄存器(计数器)方式:为每个页框配一个寄存器,记录该页最后一次被访问的时刻(或一个不断右移的位串)。淘汰时找数值最小的那个。代价是每个页框都要一个寄存器,且访存时要写。

栈方式:用一个特殊的栈保存页号。每次访问某页,就把它的页号从栈中抽出、压到栈顶。于是栈顶永远是最近使用的,栈底永远是最久未使用的,淘汰时直接取栈底。

D 是本题最强的干扰项,必须说清为什么不选它:

LRU 并不真的”对所有页排序”。 栈方式只需把被访问的页号抽出、压到栈顶,就自动维持了顺序,全程不执行任何排序算法;计数器方式也只是在淘汰时找一次最小值。选项 D 描述的是一种直觉上的印象,不是实际做法。

更关键的是教材自己的表述:王道对 LRU 代价的原话是”需要寄存器和栈的硬件支持”——这句话与选项 A 逐字对应。本题考的正是对这句话的复现。

这道题的答案容易记反(“要维护顺序”听起来太像”要排序”了),做题时回到那句原话即可。

正因为精确 LRU 代价太高,实际系统普遍使用它的近似算法——也就是下面的 Clock。

④ CLOCK 时钟置换算法(NRU)

Clock 是 LRU 的低成本近似。它只用一位访问位 A,就近似地表达了”最近有没有被用过”。

规则:

  1. 把所有页框组织成一个循环队列,用一个指针指向下一个待检查的页框。
  2. 页面被访问时,硬件自动把 A 置 1(这一步是免费的,硬件顺手做的)。
  3. 需要淘汰时,指针开始扫描:
    • 遇到 A = 0 → 淘汰它,指针后移
    • 遇到 A = 1 → 把 A 置 0(给它”第二次机会”),指针后移,继续扫描
  4. 若一圈下来全是 1,则第二圈时它们都已被置 0,必然能选出一个。所以最多扫描两轮。

“给第二次机会”是理解 Clock 的关键:A = 1 说明它最近被用过,先饶它一次;如果转了一圈回来它还没被再次访问(A 仍是刚才置的 0),就说明它确实不活跃了,可以淘汰。

⑤ 改进型 CLOCK 算法

疑问点:改进型时钟置换算法的记忆方法

需要一个最通透、最容易理解的记忆方法。

最通透的记法只有一句:把 (A, M) 看成一个两位二进制数,谁小先淘汰谁。

优先级(A, M)二进制值含义
1(最先淘汰)(0, 0)0最近没访问、也没修改 → 不用写回,白捡
2(0, 1)1最近没访问,但改过 → 该淘汰,但要写回
3(1, 0)2最近访问过,没改 → 还可能要用,先留着
4(最后淘汰)(1, 1)3最近访问过且改过 → 最不该动

而这个顺序不是硬记的,它有一条明确的道理——为什么 A 是高位、M 是低位:

A 说的是”这页还会不会再用”(未来的价值);M 说的是”淘汰它要花多少代价”(写不写回)。

价值优先于代价。 一个马上还要用的页,哪怕淘汰它零成本,也不该淘汰——因为淘汰了马上又要调回来。反过来,一个确实不会再用的页,哪怕要写回一次,也值得淘汰。

所以 A 位的分量必然重于 M 位,把它放在高位,二进制大小顺序就自动等于淘汰优先级顺序。记住”价值优先于代价”这六个字,四种情况的排序就永远不会记反。

扫描过程(最多四轮):

轮次找什么是否清 A
第 1 轮(0, 0)否
第 2 轮(0, 1)是(把扫过的 A 置 0)
第 3 轮(0, 0)否(此时 A 已被第 2 轮清过)
第 4 轮(0, 1)是

第 1、2 轮先按原样找;若都没找到,说明当时所有页的 A 都是 1,而第 2 轮已经把它们全部清零,第 3 轮必然能找到。

改进型 Clock 相对普通 Clock 的收益是:优先淘汰不需要写回的页,减少磁盘 I/O。

2021 统考真题完整解法

疑问点:页、页框、页帧的对应关系

54.【2021 统考真题】某请求分页存储系统的页大小为 4KB,按字节编址, 系统给进程 P 分配 2 个固定的页框,并采用改进型 Clock 置换算法,进程 P 页表的部分内容如下:

页号页框号存在位访问位修改位
220H000
360H110
480H111

若 P 访问虚拟地址为 02A01H 的存储单元,则经地址变换后得到的物理地址是( )。 A. 00A01H B. 20A01H C. 60A01H D. 80A01H

另需厘清”页框 / 页帧”等名词的对应关系。

答案 C:60A01H。

先把名词对齐——这几个词说的是同一样东西,只是译法不同:

页框 = 页帧 = 物理块 = 内存块 = frame

而页 = 页面 = page,指的是逻辑地址空间被分成的块。

一句话对应:页是”逻辑的”,页框是”物理的”,两者大小相同,页表记录的就是”第几页放在第几个页框里”。

解题四步:

第一步:拆地址。 页大小 ,按字节编址 → 页内偏移占 12 位 = 3 个十六进制位。页号页内偏移所以页号 = 2,页内偏移 = A01H。

第二步:查页表,发现缺页。 页号 2 的存在位为 0 → 该页不在内存,产生缺页中断,必须先置换。

这一步是本题的陷阱——不少人直接拿页表里页 2 那一行的”页框号 20H”去拼,得到 20A01H(选项 B)。但存在位为 0 意味着那个页框号是无效的(该页根本没被装入过,这一栏是残留或未定义值)。

第三步:用改进型 Clock 选淘汰页。 内存中现有页 3 (A=1, M=0) 和页 4 (A=1, M=1):

轮次找什么结果扫描后状态
第 1 轮(0,0)未找到页3 (1,0) 页4 (1,1)
第 2 轮(0,1),并清 A未找到页3 (0,0) 页4 (0,1)
第 3 轮(0,0)找到页 3—

淘汰页 3,回收其页框 60H。因为页 3 的 M = 0(未修改),不必写回外存。

第四步:装入并拼接。 页 2 被装入页框 60H:

物理地址页框号页内偏移

也可以用乘法验证: ✓

这道题把本章几乎所有考点串了一遍:地址拆分、存在位判断、缺页中断、改进型 Clock 扫描、修改位决定是否写回、页框号拼接。它是第 3 章最值得反复做的一道题。

边界

命中时要不要更新记录

这是手算题最大的失分点,各算法完全不同:

算法命中时缺页时
FIFO什么都不做(队列顺序不变)淘汰队头,新页入队尾
LRU必须更新最近使用顺序淘汰最久未用者
CLOCK把 A 置 1扫描找 A=0 者
改进型 CLOCK把 A 置 1;若是写操作还要置 M = 1四轮扫描
OPT什么都不做淘汰 nxt 最大者

记法:FIFO 看”进来的时间”,进来了就定了,命中不改变;LRU 和 Clock 看”使用情况”,所以命中必须记一笔。

Belady 异常只出现在 FIFO

只有 FIFO 会出现”页框数增加、缺页反而增多”的 Belady 异常。 LRU 和 OPT 都不会。

根本原因是 LRU 和 OPT 属于栈算法:它们保证页框数为 时的驻留集,必然包含页框数为 时的驻留集。既然内存里的页只多不少,缺页自然只减不增。

而 FIFO 的淘汰依据是”进入的先后”,与”是否还会被访问”毫无关系,不满足这个包含性质,因此可能反常。

LRU 与 Clock 的关系

Clock 是 LRU 的近似,而不是另一种思路。

  • LRU 需要知道精确的使用先后顺序(谁比谁更久没用)——代价是每次访存都要更新。
  • Clock 只需要知道**“最近有没有用过”这一位信息**——硬件置位是免费的。

用一位近似换取了实现成本的大幅下降,代价是精度降低(它区分不出”刚用过”和”半秒前用过”)。

实际系统几乎都用 Clock 系列而非精确 LRU,原因就在这里。

各算法的性能排序

OPT 是理论上限(不可实现),FIFO 是实用算法里最差的。 LRU 效果好但代价高,Clock 是工程上的最佳折中。

对照速查

算法淘汰依据可实现Belady 异常性能
OPT未来最晚使用❌否最优(标尺)
FIFO进入内存最早✅✅ 会最差
LRU过去最久未用✅(需硬件)否接近 OPT
CLOCK访问位 A = 0✅否LRU 的近似
改进型 CLOCK(A,M) 按二进制值✅否更省 I/O
改进型 Clock 淘汰顺序理由
(0,0) → (0,1) → (1,0) → (1,1)把 (A,M) 当两位二进制数,从小到大
A 为何是高位价值优先于代价:A 管”还用不用”,M 管”写不写回”

考点

  • 手算三步法,尤其”命中时各算法是否更新记录”
  • 只有 FIFO 有 Belady 异常;LRU、OPT 是栈算法故不会
  • LRU 代价高的原因是”每次访存都要更新”,只能靠硬件(选 A)
  • LRU 的两种硬件实现:寄存器(计数器)与栈(栈底即最久未用)
  • Clock 最多扫描两轮;改进型 Clock 最多四轮
  • 改进型 Clock 的顺序:(A,M) 当二进制数从小到大
  • 2021 真题:存在位为 0 时页表里的页框号无效,必须先置换

链接