回 Jason 主站

第 4 章 存储管理

地址转换、四种页面置换、工作集与虚拟内存。

来源:课程《操作系统》课件 198 张幻灯片(index.md) 风格:先类比再定义,公式可背,例题可算,面试常考点单独标注。


4.1 存储管理的目标

知识点插图:4.1 存储管理的目标
插图:4.1 存储管理的目标

4.1.1 我们要解决什么问题

知识点插图:4.1.1 我们要解决什么问题
插图:4.1.1 我们要解决什么问题

类比:内存就像图书馆的”开架阅览区”——空间有限、想看的书很多、还不能让两个读者抢同一本书。OS 作为”图书管理员”要同时做四件事:

  1. 主存分配/回收:把有限的内存切给多个程序用,回收后还能给下一个用。
  2. 地址转换/重定位:屏蔽物理细节,让程序员写代码时不用关心”我会被装到内存哪个位置”。
  3. 存储保护/共享:A 程序不能踩 B 程序的内存;但共享代码段(比如 libc)允许多程序映射同一份。
  4. 存储扩充:内存不够?让程序”看上去”有比物理内存大得多的空间——这就是后面虚存的根。

4.1.2 三级存储器层次(slide-008/009/010)

知识点插图:4.1.2 三级存储器层次(slide-008/009/010)
插图:4.1.2 三级存储器层次(slide-008/009/010)

核心矛盾:速度 vs 成本。越快的存储越贵越小,越慢的越便宜越大。

层级 名字 典型容量 访问时间 带宽 谁管
L1 寄存器 < 1 KB 0.25–0.5 ns 20–100 GB/s 编译器
L2 Cache > 16 MB 0.5–25 ns 5–10 GB/s 硬件
L3 主存 > 16 GB 80–250 ns 1–5 GB/s OS
L4 磁盘 > 100 GB 5,000,000 ns(5ms) 20–150 MB/s OS

面试要点:磁盘比主存慢 5 个数量级——这就是为什么”缺页一次代价巨大”,也是后面所有页面置换算法的优化动机。

CPU 内部本身已经有多级存储(寄存器 + Cache + 段部件 + 分页部件),分页/分段不是 OS 凭空发明,而是硬件提供的能力。

4.1.3 物理地址 vs 逻辑地址

知识点插图:4.1.3 物理地址 vs 逻辑地址
插图:4.1.3 物理地址 vs 逻辑地址
  • 物理地址:内存按字节编号,0、1、2… 是一维线性的,唯一的。
  • 逻辑地址(虚地址):用户程序用的地址,总是从 0 开始
  • 为什么不直接用物理地址
  • 用户得自己算位置(每搬一次家整个程序都要改)
  • 多道程序同时跑就乱了
  • 程序不可移植

4.2 程序装入与链接

知识点插图:4.2 程序装入与链接
插图:4.2 程序装入与链接

4.2.1 三种链接

知识点插图:4.2.1 三种链接
插图:4.2.1 三种链接
时机 谁来做 什么时候定地址
静态链接 链接器 编译完成后,所有外部引用全部解析完成
动态链接 装入时 装入内存时再解析共享库
运行时链接 进程运行中 第一次用到符号才解析(dlopen)

4.2.2 三种装入

知识点插图:4.2.2 三种装入
插图:4.2.2 三种装入
方式 程序里的地址 缺点
绝对装入 写死的物理地址 必须装到特定位置,多任务下完全不可行
可重定位装入 相对地址,装入时一次性改写 装入后不能搬家
动态运行时装入 相对地址,运行时硬件翻译 需要 MMU 硬件支持,但灵活

4.2.3 三种地址重定位

知识点插图:4.2.3 三种地址重定位
插图:4.2.3 三种地址重定位
  • 静态重定位:装入时由装入程序把”逻辑地址 + 装入基址”算成物理地址。简单不要硬件,但程序装入后搬不动
  • 动态重定位(现代 OS 都用这个):执行每条指令时由硬件重定位寄存器做加法。基址寄存器内容由 OS 用特权指令设置,程序可以随便搬家。
  • 运行时链接重定位:动态链接库地址在运行时确定。

记忆点:动态重定位是虚拟存储的基础,没有 MMU 的硬件支持就没有现代 OS。

slide-020 装入示意(原图已转 mermaid):程序里写 mov r1, [500] 引用的是程序地址空间内 500 地址;装入时把整个程序基址搬到主存 1000,那条指令”在物理上”就要去访问主存 1500:

flowchart LR
    subgraph A["a. 程序地址空间 (0~599)"]
        A1["0"] --- A2["100: mov r1, [500]"] --- A3["500: 12345"] --- A4["599"]
    end
    subgraph B["b. 内存空间 (0~256K-1)"]
        B1["0"] --- B2["1000"] --- B3["1100: mov r1, [1500]"] --- B4["1500: 12345"] --- B5["1600"] --- B6["256K-1"]
    end
    A -->|"装入程序: 基址 +1000"| B

每个程序内地址 d 装入后变成 1000 + d。这就是静态重定位的”一次性改写”,动态重定位则是 MMU 在每次访问时即时做这个加法。

4.2.4 存储保护

知识点插图:4.2.4 存储保护
插图:4.2.4 存储保护
  • 界地址寄存器(上限/下限):CPU 检查每个访问地址 ∈ [下限, 上限],越界就异常。
  • 存储保护键:每块内存有一个键,PSW 里有”钥匙”,访问时比较——配钥匙才能开门。

4.3 连续分配

知识点插图:4.3 连续分配
插图:4.3 连续分配

4.3.1 单一连续分配

知识点插图:4.3.1 单一连续分配
插图:4.3.1 单一连续分配

内存只装一个用户程序 + OS。早期单道系统用,现在没人用了。

4.3.2 固定分区

知识点插图:4.3.2 固定分区
插图:4.3.2 固定分区

把主存预先切成 N 个固定大小的分区,每个分区给一个作业用。

  • 大小相等:管理简单;但小程序浪费、大程序装不下。
  • 大小不等(多个 small/medium/large 池):缓解但不解决。

关键数据结构:MBT(存储分块表)= 分区的”大小、位置、是否已用”三元组。

优点:管理简单,硬件只需一对界地址寄存器。 缺点:必有内部碎片(程序比分区小,剩下空间浪费);并发数固定。

4.3.3 动态分区(可变分区)

知识点插图:4.3.3 动态分区(可变分区)
插图:4.3.3 动态分区(可变分区)

进程来了再动态切一块”刚好够用”的分区。

slide-037 例题(必考原型): - 物理内存 2560KB,OS 占 0–400K - 5 个进程:P1=600KB(t=10)、P2=1000KB(t=5)、P3=300KB(t=20)、P4=700KB(t=8)、P5=500KB(t=15)

slide-038 演变图(原图已转表):

阶段 事件 内存布局(自上而下)
(a) 初始 OS(0–400K) + 空闲 400K–2560K(2160K 大空闲)
(b) P1/P2/P3 装入 OS / P1(400–1000) / P2(1000–2000) / P3(2000–2300) / 空闲(2300–2560,260K)
(c) P2 离开 OS / P1 / 空洞(1000–2000) / P3 / 空闲——外部碎片首次出现
(d) P4 装入空洞 OS / P1 / P4(1000–1700) / 空洞(1700–2000,300K) / P3 / 空闲
(e) P3 离开 OS / P1 / P4 / 空洞 / 空洞(2000–2300) / 空闲——两段空洞分裂
(f) P5 装入 OS / P1 / P5(900–1000?) / P4 / 空洞 / 空洞 / 空闲——内存被切得更碎

记忆点:动态分区天然产生外部碎片——所有空闲加起来够,但每块都太小。这是后续”分页”出现的动机。

数据结构

知识点插图:数据结构
插图:数据结构
  • 已分配区表 + 未分配区表(FBT)
  • 链表法:空闲块自身存”长度 + 下一块地址”

分配算法(必背)

知识点插图:分配算法(必背)
插图:分配算法(必背)
算法 怎么找 排序方式 优点 缺点
首次适应 (First Fit) 从头找第一个能塞下的 地址递增 高地址大块得以保留;时间快 低地址越来越碎
下次适应 (Next Fit) 从上次找到的位置继续 同上 分布均匀 大块容易被切碎
最佳适应 (Best Fit) 找差距最小的 大小递增 大块得以保留 小空闲块越来越多(最碎)
最坏适应 (Worst Fit) 找最大的切 大小递减 剩下的还能用 大作业进不来

slide-046 例题对比:内存有空隙序列,进程到达顺序不同,不同算法的成败也不同。结论:没有银弹

回收时合并

知识点插图:回收时合并
插图:回收时合并

释放分区时检查上下相邻——合并相邻空闲分区(避免新生碎片)。

4.3.4 内部碎片 vs 外部碎片(高频面试)

知识点插图:4.3.4 内部碎片 vs 外部碎片(高频面试)
插图:4.3.4 内部碎片 vs 外部碎片(高频面试)
内部碎片 外部碎片
在哪 已分配分区内部没用满 分区之间的小空隙
谁产生 固定分区、分页 动态分区、分段
怎么消除 让分区粒度更细(极限就是分页) 紧凑(compaction,移动程序)

4.3.5 内存不足的应对

知识点插图:4.3.5 内存不足的应对
插图:4.3.5 内存不足的应对
  • 移动技术(紧凑):把零散程序挤到一头,腾出大空闲。必须用动态重定位,否则程序搬家就跑不起来。
  • 对换(swapping):低优先级或时间片用完的进程,整个 PCB 写到磁盘,腾内存给别人。
  • 覆盖(overlay):一个程序内部分段,主程序常驻,不常用的子程序按需替换。用户负担重,早期用,现在被虚存替代。

4.4 分页存储管理

知识点插图:4.4 分页存储管理
插图:4.4 分页存储管理

4.4.1 核心概念(slide-062 起)

知识点插图:4.4.1 核心概念(slide-062 起)
插图:4.4.1 核心概念(slide-062 起)

类比:图书馆把每本书都拆成大小一样的”册”(如 4KB),书架上每个格子也是同样大小。一本书的不同册可以放在书架的任意格子里——管理员靠一张”目录表”记录”第几册放在第几格”。

在 OS 里 在比喻里
页(Page) 逻辑空间的一块 书的一册
页框(Frame)/页架/块 物理内存的一格 书架格子
页表(PMT) 页号 → 页框号的映射表 这本书的目录

关键设计:页大小 = 页框大小,固定且通常是 2 的幂(如 4KB = 2¹²)。

4.4.2 地址转换(必背公式)

知识点插图:4.4.2 地址转换(必背公式)
插图:4.4.2 地址转换(必背公式)

逻辑地址 A,页大小 L:

页号 p = INT[A / L]
页内偏移 d = A mod L
物理地址 = 页框号 × L + d

slide-073 例题:进程页表已知,每页 1024 字节,逻辑地址 2865 求物理地址: - p = INT[2865/1024] = 2 - d = 2865 mod 1024 = 817 - 查页表,第 2 页 → 第 6 块 - 物理地址 = 6 × 1024 + 817 = 6961

4.4.3 slide-065 映射图直觉(原图已转 mermaid)

知识点插图:4.4.3 slide-065 映射图直觉(原图已转 mermaid)
插图:4.4.3 slide-065 映射图直觉(原图已转 mermaid)

三个作业的逻辑空间都被切成 1KB 等大页,物理主存按 1KB 分页框;OS 占 0–2K,下面交错装着各作业的页(作业 1 的 0/1/2 页可以散布在物理空间任意位置)。核心点:同一作业的页不要求物理连续——这就是分页的革命性突破。

flowchart LR
    subgraph J1["作业 1 逻辑 (0~2K-1)"]
        J1P0["0 页"]
        J1P1["1 页"]
        J1P2["2 页"]
    end
    subgraph J2["作业 2 逻辑 (0~3K-1)"]
        J2P0["0 页"]
        J2P1["1 页"]
        J2P2["2 页"]
    end
    subgraph J3["作业 3 逻辑 (0~1K-1)"]
        J3P0["0 页"]
    end
    subgraph M["主存 (0~10K-1)"]
        M0["0~2K: OS"]
        M1["3K: 作业2(0页)"]
        M2["4K: 作业1(1页)"]
        M3["5K: 作业2(1页)"]
        M4["6K: 作业1(0页)"]
        M5["7K: 作业3(0页)"]
        M6["8K: 作业2(2页)"]
        M7["9K: 作业1(2页)"]
    end
    J1P0 -.-> M4
    J1P1 -.-> M2
    J1P2 -.-> M7
    J2P0 -.-> M1
    J2P1 -.-> M3
    J2P2 -.-> M6
    J3P0 -.-> M5

页在主存中的次序由分配时决定——作业 1 三页可能错落在 6K/4K/9K,作业 2/3 同理,但每个作业自己看是连续的(0、1、2 页)。

4.4.4 页表的烦恼 → 多级页表

知识点插图:4.4.4 页表的烦恼 → 多级页表
插图:4.4.4 页表的烦恼 → 多级页表

问题:32 位地址空间 + 4KB 页 → 1M 个页表项 × 4B = 每进程 4MB 页表。100 个进程就 400MB!

多级页表思路: - 把页表本身也分页(页表页) - 一级页目录指向各级页表页 - 只有当前用到的页表页才装入主存

代价:访问数据要 3 次访存(页目录 → 页表页 → 数据)。TLB 就是来缓解这个的

4.4.5 反向页表(IPT)

知识点插图:4.4.5 反向页表(IPT)
插图:4.4.5 反向页表(IPT)

思路反过来:不按逻辑页号建表,按物理页框号建表。 - 表大小 = 物理页框数(与逻辑空间无关) - 用 hash 把虚拟页号映射到表项 - 64MB 主存 + 4KB 页只需 64KB 反向页表——非常省

4.4.6 TLB(快表/Translation Lookaside Buffer)

知识点插图:4.4.6 TLB(快表/Translation Lookaside Buffer)
插图:4.4.6 TLB(快表/Translation Lookaside Buffer)

类比:图书管理员每查一次页表都要去远处书柜(主存)翻目录太慢,就在桌上摊开最近常用的几条目录——这就是 TLB,本质是关联高速缓存。

关键公式 — 平均访存时间 EAT

EAT = (1 - p) × (TLB + 主存)
    + p × (TLB + 2 × 主存)
  • p = TLB 缺失率
  • TLB = 快表查询时间(如 20ns)
  • 主存 = 一次访存时间(如 100ns)
  • TLB 命中:1 次主存(直接拿数据)
  • TLB 缺失:先访存查页表 + 再访存取数据 = 2 次主存

:TLB=20ns,主存=100ns,命中率 80%: - EAT = 0.8 × (20+100) + 0.2 × (20+200) = 96 + 44 = 140ns

4.4.7 分页的优缺

知识点插图:4.4.7 分页的优缺
插图:4.4.7 分页的优缺
  • 优点:无外部碎片;只有每个进程最后一页有内部碎片(最多半页)
  • 缺点:必须一次全部装入;硬件支持成本高;不便于按逻辑共享

4.5 分段存储管理

知识点插图:4.5 分段存储管理
插图:4.5 分段存储管理

4.5.1 为什么分段(slide-082 起)

知识点插图:4.5.1 为什么分段(slide-082 起)
插图:4.5.1 为什么分段(slide-082 起)

分页是 OS 强加的等大切分(用户不感知,物理单位)。分段是程序员主动按逻辑模块切(用户感知段号,逻辑单位)。

典型分段:代码段 / 数据段 / 栈段 / 堆段 / 共享库段。每段功能独立、长度天然不同。

4.5.2 段式逻辑地址(二维)

知识点插图:4.5.2 段式逻辑地址(二维)
插图:4.5.2 段式逻辑地址(二维)
逻辑地址 V = (S, W)
S = 段号
W = 段内偏移

vs 分页的一维地址:分页地址 A 是一维的,硬件自动切成 (页号, 页内偏移);分段则程序员显式给出 S 和 W。

4.5.3 段表(slide-090 例子)

知识点插图:4.5.3 段表(slide-090 例子)
插图:4.5.3 段表(slide-090 例子)

每个进程一张段表,每行:

段号 段长 段基址(在主存中的起始地址)

slide-090 的作业 1 有 5 个段(M、X、Y、A、B),段表记录每段长度和被装入主存的起点。段表寄存器保存当前进程段表的起址和长度。

slide-090 例(原图已转表)——作业 1 的段表与主存装入:

逻辑段号 段名 段长 段地址(主存起点) 主存中位置
0 M K 3200 3200 起
1 X P 1500 1500 起
2 Y L 6000 6000 起
3 A N 8000 8000 起
4 B S 5000 5000 起

注意 5 个段在主存中完全不连续(1500、3200、5000、6000、8000),但作业 1 自己看仍是按段号 0~4 索引的二维地址空间——这就是分段在主存里的真实形态。

4.5.4 地址转换(slide-088)

知识点插图:4.5.4 地址转换(slide-088)
插图:4.5.4 地址转换(slide-088)
1. 用 S 查段表 → 得 (段长, 段基址)
2. 检查 W < 段长?否则越界异常
3. 物理地址 = 段基址 + W

比分页多一步段长检查,安全性更高。

4.5.5 分段优缺

知识点插图:4.5.5 分段优缺
插图:4.5.5 分段优缺
分页 分段
切分单位 物理(OS 决定,等大) 逻辑(用户决定,不等长)
地址 一维 二维
共享/保护 难(按物理页粒度) 易(按逻辑模块)
碎片 内部碎片 外部碎片(段长不等)

4.6 段页式

知识点插图:4.6 段页式
插图:4.6 段页式

思路:段提供逻辑结构(共享、保护、模块化),页解决主存分配(无外部碎片)。

4.6.1 划分

知识点插图:4.6.1 划分
插图:4.6.1 划分
  • 主存:等大页框(与分页相同)
  • 逻辑:先分段(按程序模块)→ 每段内部再分页

4.6.2 三维逻辑地址

知识点插图:4.6.2 三维逻辑地址
插图:4.6.2 三维逻辑地址
V = (S, P, d)
S = 段号
P = 段内页号
d = 页内偏移

4.6.3 数据结构(三层索引)

知识点插图:4.6.3 数据结构(三层索引)
插图:4.6.3 数据结构(三层索引)
  • 作业表:作业号 → 段表起址
  • 段表:段号 → 该段页表起址 + 段是否在内存
  • 页表:页号 → 页框号 + 页是否在内存

4.6.4 地址转换

知识点插图:4.6.4 地址转换
插图:4.6.4 地址转换
  1. 查 TLB(带 S+P 联合作为 key),命中直接拼地址
  2. 缺失 → 段表查页表起址 → 页表查页框号
  3. 段不在 → 缺段中断;页不在 → 缺页中断
  4. 共需 3 次访存(段表 + 页表 + 数据),TLB 命中可降为 1 次

4.6.5 优缺

知识点插图:4.6.5 优缺
插图:4.6.5 优缺
  • 优点:兼具段的共享/保护和页的高利用率
  • 缺点:每段平均一页内部碎片(比纯分页多);硬件复杂;管理开销大

4.7 虚拟存储管理(重头戏)

知识点插图:4.7 虚拟存储管理(重头戏)
插图:4.7 虚拟存储管理(重头戏)

4.7.1 局部性原理(虚存的理论基石)

知识点插图:4.7.1 局部性原理(虚存的理论基石)
插图:4.7.1 局部性原理(虚存的理论基石)

类比:你在一段时间内只看图书馆的几本相关书(空间局部性),一本书翻开后短时间内反复用(时间局部性)。所以——没必要把整个图书馆都搬到桌上

局部性 含义 典型来源
时间局部性 刚访问的指令/数据短时间内还会访问 循环、函数反复调用
空间局部性 访问了某地址,附近地址也会被访问 顺序执行、数组/记录

结论:进程不必全部装入内存就能跑——只把”当前要用的部分”调进来即可。剩下放在磁盘,需要时再换。

4.7.2 虚拟存储器定义

知识点插图:4.7.2 虚拟存储器定义
插图:4.7.2 虚拟存储器定义

在具有层次结构存储器的计算机系统中,采用自动实现部分装入和部分对换功能,为用户提供一个比物理内存容量大得多的、可寻址的”内存储器”。

类比:图书馆的”开架阅览区”(主存)只有 100 本书的座位,但全馆(外存)有 10 万本。读者(程序员)写借书条时假装可以借任何一本——管理员(OS+MMU)后台帮他换书,他感受不到。

4.7.3 物质基础(实现条件)

知识点插图:4.7.3 物质基础(实现条件)
插图:4.7.3 物质基础(实现条件)
  • 较大容量的辅存(页文件 / swap)
  • 一定容量的主存
  • 地址变换机构(MMU)

虚拟空间限制: - 指令地址场长度(主要原因)— 32 位机器最多 4GB 虚拟空间 - 外存大小

4.7.4 请求分页

知识点插图:4.7.4 请求分页
插图:4.7.4 请求分页

实存分页要求”一次全部装入”,虚存改为”用到才装”。

页表扩展(必备字段):

字段 含义
页号 逻辑页号
内存块号 物理页框号
驻留位(中断位) 该页是否在主存
修改位(M / Dirty) 在内存时被改过吗(决定是否要写回磁盘)
引用位(R) 最近访问过吗(用于 LRU/CLOCK)
保护位 读/写/执行权限

外页表:页号 → 磁盘物理地址(系统启动时建立,存磁盘上)。

4.7.5 缺页中断处理流程(slide-115,必背 7 步)

知识点插图:4.7.5 缺页中断处理流程(slide-115,必背 7 步)
插图:4.7.5 缺页中断处理流程(slide-115,必背 7 步)
  1. 挂起当前进程
  2. 外页表,找到该页在磁盘的位置
  3. 主存有空闲页框?有 → 转 6
  4. 无空闲 → 用置换算法选淘汰页;检查修改位
  5. 修改位=1 → 写回磁盘原位
  6. 调页:把目标页装入分配到的页框,修改页表
  7. 返回断点,重新执行被中断的指令

关键细节:是”重新执行”那条访问指令,不是从断点继续——因为指令可能正执行到一半就缺页。

4.7.6 缺页率公式

知识点插图:4.7.6 缺页率公式
插图:4.7.6 缺页率公式
缺页率 f = F / A
其中  A = 总访问次数 = S(成功)+ F(缺页)

slide-155 例题(程序局部性威力):3 页内存,1 页存程序,2 页存数据;数组 100×100,每页放 200 个数。

// 程序 A:按行遍历 — 与存储顺序一致
for (i=1; i<=100; i++)
  for (j=1; j<=100; j++)
    A[i][j] = 0;
// 缺页 = 10000/200 = 50 次

// 程序 B:按列遍历 — 与存储顺序相反
for (j=1; j<=100; j++)
  for (i=1; i<=100; i++)
    A[i][j] = 0;
// 缺页 = 10000/2 = 5000 次  ← 100 倍差距!

面试常考:相同算法,仅访问顺序不同 → 性能差 100 倍。这就是”程序员要懂局部性”的核心理由。

影响缺页率的因素: - 内存页框数(多 → 缺页少) - 页大小(小 → 页表大但缺页粒度细) - 替换算法 - 程序特性(局部性好坏)

4.7.7 页面置换算法(必考重点)

知识点插图:4.7.7 页面置换算法(必考重点)
插图:4.7.7 页面置换算法(必考重点)

(1) OPT — 最佳置换(理论基线)

知识点插图:(1) OPT — 最佳置换(理论基线)
插图:(1) OPT — 最佳置换(理论基线)
  • 淘汰未来最久不被访问的页
  • 无法实现(要预知未来)
  • 用作其他算法的对比基准

(2) FIFO — 先进先出

知识点插图:(2) FIFO — 先进先出
插图:(2) FIFO — 先进先出
  • 淘汰进入内存最早的页
  • 实现简单:维护页框队列
  • Belady 异常(slide-160):访问串 4,3,2,1,4,3,5,4,3,2,1,5——分配 3 个页框 vs 4 个页框,4 个页框反而缺页更多!原因:FIFO 与局部性矛盾,被淘汰的不一定是不再用的。

(3) LRU — 最近最少使用

知识点插图:(3) LRU — 最近最少使用
插图:(3) LRU — 最近最少使用
  • 淘汰最久没被访问的页(基于”最近用过的还会用”的假设)
  • 不会有 Belady 异常
  • 实现成本高:
  • 页面淘汰队列(每访问一次调整队列)
  • 引用位法(NRU 近似)
  • 计数法(LFU 变体)
  • 计时法 / 老化算法(多位寄存器右移)

slide-165–166 例子:3 个页框,访问 4,3,0,4,1,1,2,3,2,淘汰序列演变看得很清楚。

(4) CLOCK — 时钟(OS 实际常用)

知识点插图:(4) CLOCK — 时钟(OS 实际常用)
插图:(4) CLOCK — 时钟(OS 实际常用)

类比:把所有页框排成一圈,有一根指针(时钟指针)。

基本 CLOCK:
  - 每页有引用位 R
  - 访问页时 R=1
  - 淘汰时从指针位置扫描:
    - 遇 R=1 → R=0,跳过
    - 遇 R=0 → 选中淘汰,指针前进

改进 CLOCK(结合修改位 M 和引用位 R 的 4 种组合): 1. (R=0, M=0) — 最优先淘汰(既老又干净,不用写回) 2. (R=0, M=1) — 老但脏(要写回) 3. (R=1, M=0) — 最近用但干净 4. (R=1, M=1) — 最近用且脏

扫描两遍:第一遍找 (0,0);找不到则第二遍找 (0,1),过程中把 R 清 0。

(5) Second Chance — 第二次机会

知识点插图:(5) Second Chance — 第二次机会
插图:(5) Second Chance — 第二次机会

是 FIFO 的改进,碰到 R=1 的”重新当作新页放队尾”。CLOCK 是它的环形版本。

slide-180 性能比较(同访问串 2,3,2,1,5,2,4,5,3,2,5,2,3 个页框)

知识点插图:slide-180 性能比较(同访问串 2,3,2,1,5,2,4,5,3,2,5,2,3 个页框)
插图:slide-180 性能比较(同访问串 2,3,2,1,5,2,4,5,3,2,5,2,3 个页框)
算法 缺页次数
OPT 6
LRU 7
CLOCK 8
FIFO 9

记忆:OPT < LRU < CLOCK < FIFO(一般情况下)。

4.7.8 工作集模型 / 抖动(高频考点)

知识点插图:4.7.8 工作集模型 / 抖动(高频考点)
插图:4.7.8 工作集模型 / 抖动(高频考点)

类比:你写论文时桌上摊开的几本最相关的书 = 工作集。窗口尺寸 Δ 就是你”最近多久内用过的算”——比如最近 30 分钟用过的书都摆桌上。

工作集定义

知识点插图:工作集定义
插图:工作集定义
W(t, Δ) = {进程在时间窗口 (t-Δ, t] 内访问过的页面集合}

slide-185 经典图: - 引用串 …2 6 1 5 7 7 7 5 1 6 2 3 4 1 2 3 4 4 4 3 4 3 4 4 1 3 2 3 4 4 4 3 4 4 4… - 在 t1 时刻,向前看 Δ 个引用 → WS(t1) = {1, 2, 5, 6, 7} - 在 t2 时刻 → WS(t2) = {3, 4}(局部性变了,工作集也变了)

驻留集(resident set):进程实际驻留在内存的页集合。

工作集策略

知识点插图:工作集策略
插图:工作集策略
  1. 监视每个进程的工作集
  2. 仅当工作集全部装入内存时才让进程运行
  3. 定期清出”不在工作集”的页

抖动(Thrashing)

知识点插图:抖动(Thrashing)
插图:抖动(Thrashing)
  • 现象:CPU 大部分时间在处理缺页中断而非执行用户代码
  • 原因:分给进程的页框 < 工作集 → 频繁淘汰马上又要用的页 → 又缺页 → 恶性循环
  • 对策
  • 工作集模型(保证驻留集 ≥ 工作集)
  • 缺页频率(PFF)算法:动态调整驻留集大小
  • 实在不行:挂起一些进程(减少多道程度)

缺页频率(PFF)算法

知识点插图:缺页频率(PFF)算法
插图:缺页频率(PFF)算法

监测连续缺页之间的时间间隔: - 间隔 > 阈值 τ → 工作集变了,移出窗口内未引用的页 - 间隔 < 阈值 τ → 进程页框不够,加分配

4.7.9 页面分配/置换策略组合

知识点插图:4.7.9 页面分配/置换策略组合
插图:4.7.9 页面分配/置换策略组合
分配 置换 典型应用
固定 + 局部 经典做法 简单 OS
可变 + 全局 灵活 SVR4
可变 + 局部 平衡 Windows NT
固定 + 全局 不合理 不用

4.8 章末速查 + 计算题模板

知识点插图:4.8 章末速查 + 计算题模板
插图:4.8 章末速查 + 计算题模板

4.8.1 必背公式

知识点插图:4.8.1 必背公式
插图:4.8.1 必背公式
分页地址转换:
  p = INT[A / L]
  d = A mod L
  物理地址 = 页框号 × L + d

平均访存时间(带 TLB):
  EAT = (1-p)(TLB + Mem) + p(TLB + 2 × Mem)

缺页率:
  f = F / A = 缺页次数 / 总访问次数

多级页表项数:
  虚拟空间 / 页大小 = 一级页表项数

4.8.2 地址转换计算题模板

知识点插图:4.8.2 地址转换计算题模板
插图:4.8.2 地址转换计算题模板

题:32 位逻辑地址,页大小 4KB,进程页表如下,求逻辑地址 0x12345 的物理地址。

  1. 页大小 4KB = 2¹² → 偏移占低 12 位
  2. 0x12345 的二进制低 12 位是偏移 d,高 20 位是页号 p
  3. 0x12345 = 0001 0010 0011 0100 0101,p = 0x12 = 18, d = 0x345
  4. 查页表第 18 项 → 假设得页框号 0x7
  5. 物理地址 = 0x7 × 0x1000 + 0x345 = 0x7345

4.8.3 缺页计算题模板

知识点插图:4.8.3 缺页计算题模板
插图:4.8.3 缺页计算题模板

题:进程分配 3 个页框,访问串 7,0,1,2,0,3,0,4,2,3,0,3,2,问 OPT/LRU/FIFO 各缺页几次?

固定流程: 1. 画三行表格(每个页框一行)+ 顶部访问序列 2. 每访问一页,已在内存 → 命中(标 H);不在 → 缺页(标 F),按算法选淘汰页 3. 数 F 的总数

OPT 选淘汰的诀窍:往后看,谁最远才被用就淘汰谁;后面再也不用的优先淘汰。 LRU 选淘汰的诀窍:往前看,谁最久没用就淘汰谁。 FIFO:维护进入顺序队列,淘汰队首。

4.8.4 概念区分速查

知识点插图:4.8.4 概念区分速查
插图:4.8.4 概念区分速查
容易混 要点
内部 vs 外部碎片 内部 = 分区没用满;外部 = 分区的小空隙
分页 vs 分段 物理 vs 逻辑;一维 vs 二维;OS 决定 vs 程序员决定
TLB vs Cache TLB 缓存”页表项”;Cache 缓存”数据”
缺页中断 vs 越界 缺页 → 调页继续跑;越界 → 杀进程
工作集 vs 驻留集 工作集 = 应该在的;驻留集 = 实际在的
Belady 异常 仅 FIFO 会出现;LRU/OPT 不会
OPT 不能实现的原因 要预知未来访问串

4.8.5 面试高频问

知识点插图:4.8.5 面试高频问
插图:4.8.5 面试高频问
  1. 为什么有了分页还要分段?分页解决物理利用率,分段解决逻辑结构(共享、保护、动态扩展)。
  2. TLB 命中率为什么能这么高?局部性原理 + 关联高速缓存的并行查找。
  3. 缺页时为什么是”重新执行指令”?指令可能 fetch 一半就缺页,操作数可能跨页,状态难恢复 → 整条指令重做最简单。
  4. 抖动怎么解决?工作集 + PFF + 必要时挂起进程降低多道程度。
  5. 写时复制(COW)和虚存什么关系?fork 后父子进程共享物理页,写入时才复制——是请求分页的延伸应用。
  6. 为什么大多数 OS 选 CLOCK 而不是 LRU?真正的 LRU 维护开销太高(每次访问都要更新顺序);CLOCK 用引用位近似,性能接近 LRU 但开销小得多。

索引(按 slide 号回查 index.md)

知识点插图:索引(按 slide 号回查 index.md)
插图:索引(按 slide 号回查 index.md)
  • 三级层次:slide-008/009/010
  • 重定位类型:slide-17~21
  • 动态分区例题:slide-37/38(含 5 进程表 + 6 步演变)
  • 适应算法:slide-44~49
  • 移动/对换/覆盖:slide-53~60
  • 分页基础:slide-62~75
  • 分页地址转换例:slide-72/73
  • 分页映射图:slide-65
  • 分段:slide-82~98
  • 段表/段地址转换:slide-87/88/90
  • 虚存概念:slide-100~106
  • 缺页处理 7 步:slide-115
  • 多级页表 / 反向页表:slide-119~126
  • TLB:slide-129
  • 段页式:slide-131~141
  • 局部性 / 缺页率:slide-154~156
  • OPT/FIFO/LRU/CLOCK:slide-158~180
  • 工作集:slide-185~191
  • PFF:slide-196/197

对话补充:Ch4 易错点 + 考场金句(P0 重灾区)

必背 4 大公式速查

分页地址转换:
  页号 p = ⌊A / L⌋, 页内偏移 d = A mod L
  物理地址 = f × L + d

TLB EAT:
  EAT = T_t + (2 − p) × T_m

缺页 EAT:
  EAT = (1 − f) × T_m + f × T_缺页

缺页率:
  f = 缺页次数 F / 总访问次数 A

反直觉震撼:缺页率 0.1% = 慢 100 倍

T_m = 100 ns, T_缺页 = 10 ms = 10⁷ ns:

缺页率 f EAT
0% 100 ns
0.1% 10100 ns(慢 100 倍)
1% 100099 ns(慢 1000 倍)

结论:所以现代 OS 拼命把 f 压到 0.001% 以下——这是 4 大置换算法的存在意义。

4 大置换算法终极对比

算法 淘汰规则 实现 Belady? 优劣
OPT 未来最久不用 不可实现 理论最优,作为基准
FIFO 最早进入 队列 ✅ 有! 简单,但有 Belady 异常
LRU 最近最久未使用 栈/链表 性能好,实现成本高
CLOCK 引用位 R 近似 LRU 环形 + 指针 现代 OS 实用方案

Belady 异常(必考反直觉点)

经典反例:序列 1,2,3,4,1,2,5,1,2,3,4,5

页框 FIFO 缺页
m=3 9
m=4 10 ← 反而更多

为什么:FIFO 只看进入时间,不看活跃度——多一个页框可能让常用页(1, 2)晚被淘汰,反而让”该淘汰的”提前出局。

关键考点(必背):

OPT、LRU 没有 Belady 异常,FIFO 有。 因为 OPT 看未来、LRU 看过去活跃度,驻留集随页框单调扩张——多一个页框只会”多保留有用的页”。

LRU 与 FIFO 关键区别(高频辨析)

FIFO LRU
淘汰依据 进入时间(不变) 最近使用时间(每次更新)
命中时是否更新顺序 不更新 更新到最新
符合局部性原理
Belady 异常

口诀“FIFO 看进来,LRU 看用过”

分页 vs 分段 vs 段页式终极对比

分页 分段 段页式
用户视角 一维 二维 (S, d) 二维
OS 视角 一维 二维 三维 (S, P, d)
大小 固定页 可变段 可变段 + 固定页
碎片 内部 外部 内部(段内最后一页)
访存次数(无 TLB) 2 2 3
共享

口诀“分页死板省内存,分段灵活便共享,段页式两全代价高”

内/外部碎片判断口诀

被分了用不完 = 内部碎片(分配单元内部浪费) 分不出去 = 外部碎片(空闲单元间小到没法分)

Ch4 易错点 Top 10

  1. 物理地址停在表达式9×4096+308 必须算成 37172——“答案不完整”会扣分
  2. EAT 公式两版混淆:含 TLB vs 含缺页是两个公式,看清题目场景
  3. LRU 命中时忘了把页移到最新——常见错把 FIFO 当 LRU 用
  4. Belady 误以为通用现象:只 FIFO 有,OPT/LRU 没有
  5. 缺页率分母错:是”总访问次数”不是”装入次数”
  6. 碎片方向反:被分了用不完=内部,分不出去=外部
  7. 段页式访存答 2:实际 3 次(段表 + 页表 + 数据)
  8. 抖动 = 缺页:抖动是”持续大量缺页导致 CPU 干不了活”,单次缺页不算抖动
  9. TLB 命中率 vs 未命中率搞混:题面给哪个,公式相应调整
  10. 页内偏移 d 没检查 < L:sanity check 必做

局部性原理(必背简答)

含义 例子
时间局部性 最近访问的,很可能马上又访问 循环、局部变量
空间局部性 访问某地址,很可能马上访问相邻地址 数组、顺序代码

核心一句话虚拟存储基于局部性原理——只把”当前用的页”装内存,其他放磁盘按需调入

抖动(Thrashing)的因果链

驻留集 < 工作集 → 持续大量缺页 →
CPU 大量时间处理缺页中断而非用户代码 →
进程间互相抢页(互相淘汰对方的页) →
CPU 利用率急剧下降 → 系统看似很忙但完不成什么活

3 种解法:工作集模型 / PFF 缺页频率控制 / 挂起部分进程降低多道程度

Ch4 简答考场金句速查

答(金句)
为什么需要虚拟内存 物理内存有限但程序总虚拟空间可能远大;基于局部性原理,只装当前用的页,让小内存跑大程序
TLB miss 时怎么办 从内存查完整页表,把这条结果填入 TLB,再访存取数据,未命中需 2 次访存
为什么 FIFO 有 Belady 而 LRU 没 FIFO 只看进入时间与活跃度无关,扩张时驻留集可能完全不同;LRU 看活跃度,驻留集随页框单调扩张
抖动怎么避免 工作集模型动态分配页框;PFF 缺页频率控制;挂起部分进程降低多道程度
为什么页大小是 2 的幂 地址计算用位运算(移位 + 与)代替除法/取模,硬件实现快几个数量级