图 1:前六步推演网格,前六访全部缺页。
先说明属性:这是一篇历史笔试题复盘,题目出自本地留存的早年笔试卷(选择题),与任何公司的当前招聘无关。题目很短:一个进程分到 3 个主存块,页面访问序列是 1、2、3、4、1、2、5、1、2、3、4、5,用 LRU 置换算法,缺页多少次?选项 9、10、11、12。这类题不考知识点记忆,考的是十二步推演一步不乱的耐心,外加一个最容易踩的规则细节。
先把 LRU 规则钉死,尤其是命中也要动。
LRU(最近最久未使用)的规则:缺页且块已满时,淘汰当前最久没被访问的那一页。关键细节是——命中同样算一次「使用」,要把命中页挪到「最新」位置。很多人只在缺页时维护顺序,命中不动,结果从第 8 步开始全错。推演时给三个块里的页面排一个「从最久未用到最新」的队列,每一步都要更新这个顺序。
十二步逐格推演。
- 第 1~3 步:访问 1、2、3,块是空的,三次缺页,块内(久→新)为 1、2、3。
- 第 4 步:访问 4,块满,淘汰最久未用的 1,缺页。块内 2、3、4。
- 第 5 步:访问 1,1 刚被淘汰,缺页,淘汰 2。块内 3、4、1。
- 第 6 步:访问 2,同理缺页,淘汰 3。块内 4、1、2。
- 第 7 步:访问 5,缺页,淘汰 4。块内 1、2、5。
- 第 8 步:访问 1,1 在块里,命中!把 1 挪到最新。块内 2、5、1。
- 第 9 步:访问 2,命中,挪到最新。块内 5、1、2。
- 第 10 步:访问 3,缺页,淘汰当前最久未用的 5。块内 1、2、3。
- 第 11 步:访问 4,缺页,淘汰 1。块内 2、3、4。
- 第 12 步:访问 5,缺页,淘汰 2。块内 3、4、5。
数一遍:缺页发生在第 1~7 步和第 10~12 步,共 10 次,只有第 8、9 两步命中。答案是 10。
写段代码复核,顺便对比 FIFO 和 OPT。
手工推演容易眼花,用 Node.js 22.15.0 把三个算法都模拟一遍(node lru.js),LRU 每步打印块内状态:
const seq = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5];
function lru(seq, frames) {
const mem = []; let faults = 0; const rows = [];
for (const p of seq) {
const i = mem.indexOf(p);
if (i >= 0) { // 命中:挪到最新位置
mem.splice(i, 1); mem.push(p);
rows.push([p, false, [...mem]]); continue;
}
faults++;
if (mem.length === frames) mem.shift(); // 淘汰队首:最久未用
mem.push(p); rows.push([p, true, [...mem]]);
}
return { faults, rows };
}
实测输出(末三行):
LRU 缺页: 10
FIFO 缺页: 9
OPT 缺页: 7
代码逐行推演与手工一致,LRU 确实是 10 次。
图 2:十二步完整推演表,10 个青色 × 就是答案。
这组数字里藏着两个反直觉的考点。
第一,这条序列上 FIFO 只要 9 次,反而比 LRU 的 10 次少——LRU 是「经验的优等生」,不是任何序列上都赢,它赌的是「最近用过的还会再用」,遇到 1、2、3、4、5 这种周期性扫视的序列就会被坑:每次要用的页恰好都是刚被淘汰的那一页。第二,OPT(最优置换)只要 7 次:它淘汰的是「未来最迟才会再用」的页,需要预知整个序列,实际系统做不到,所以它是理论下界,专门用来给其他算法当参照物。答题时把三个数字并排摆出来,立刻就从「会算」升级为「懂权衡」。
再补一个相关的冷知识作为收尾弹药:FIFO 还存在 Belady 异常——物理块增多,缺页反而可能变多;LRU 属于栈式算法,没有这个问题。笔试里如果选项出现「增加块数一定减少缺页」,针对 FIFO 就是错的。
顺嘴一提:真机里的 LRU 是「近似」的。
严格的 LRU 要求每次访问都更新全量顺序,硬件代价太高,真实操作系统用的是它的近似版:给每页配一个访问位,页被访问时硬件置位,置换时用时钟算法(Clock)转圈扫描,遇到访问位为 0 的页就淘汰,为 1 就清掉再放它一马。效果接近 LRU,开销却只是个环形指针——笔试考严格推演,工程里跑的是近似,两个口径都要知道。
复盘这类题的方法论只有一条:别怕麻烦,画表。表头写序列,三行写块内容,一行标缺页,十二格填完答案自然浮出来,比脑子里空转可靠得多。你当年是画表派还是心算派?有没有在「命中要不要更新顺序」上栽过跟头?欢迎对答案。