考研学习
知识点讲义操作系统

第 3 章 内存管理

408 操作系统第 3 章 内存管理复习讲义,涵盖核心概念、重点考点与典型题型。

Last updated on

先看一张整章地图:

为什么要管理内存
      │
      ├── 地址问题:逻辑地址 → 物理地址
      │
      ├── 分配问题
      │     ├─ 连续分配
      │     │   ├─ 单一连续
      │     │   ├─ 固定分区
      │     │   └─ 动态分区
      │     │        └─ FF / NF / BF / WF
      │     │
      │     └─ 非连续分配
      │         ├─ 分页 ★★★★★
      │         ├─ 分段 ★★★
      │         └─ 段页式 ★★★
      │
      └── 内存不够怎么办?
            ↓
          虚拟内存 ★★★★★
            │
            ├─ 请求分页
            ├─ 缺页异常 ★★★★★
            ├─ 页框分配
            ├─ 页面置换 ★★★★★
            │   ├─ OPT
            │   ├─ FIFO
            │   ├─ LRU
            │   └─ CLOCK
            ├─ 抖动
            ├─ 页框回收
            └─ 内存映射文件

从考研角度,建议把本章理解成一句话:

操作系统想办法把进程看到的“自己的连续大内存”,映射到实际上零零散散、有限而且被许多进程共享的物理内存。

一、先搞懂:为什么操作系统一定要管理内存?

如果只有一个程序运行,事情非常简单:

内存
┌──────────────┐
│ 操作系统      │
├──────────────┤
│ 用户程序      │
└──────────────┘

但多道程序出现以后:

内存
┌──────────────┐
│ 操作系统      │
├──────────────┤
│ 进程 A        │
├──────────────┤
│ 进程 B        │
├──────────────┤
│ 进程 C        │
└──────────────┘

马上出现几个问题:

  1. A、B、C 分别放哪里?
  2. A 能不能覆盖 B 的数据?
  3. A 程序中写的地址 1000 到底对应物理内存哪个位置?
  4. 某进程退出以后,空间怎么回收?
  5. 空闲空间散成很多小块怎么办?
  6. 一个 10GB 程序能不能在只有 8GB 内存的机器上运行?
  7. 不同进程能否共享相同代码?

因此资料把内存管理的核心功能概括为:内存分配与回收、地址映射、内存保护与共享、内存扩充。

二、逻辑地址、物理地址与地址重定位

这是后面分页、分段、虚拟内存的地基。

1. 物理地址

物理地址就是真正送到主存地址总线上的地址。

假设主存中:

0
...
10000   ← 某进程实际装在这里
10001
...

那么 10000 就是物理地址。

2. 逻辑地址

程序员写程序的时候当然不可能提前知道:

“以后操作系统会把我的程序恰好装到物理地址 10000。”

所以程序中使用的是相对于自身地址空间的地址:

0
1
2
...
500
...

这就是逻辑地址。

例如:

进程自己的逻辑地址空间

0       main()
100
200
500     x
...

但进程可能实际被操作系统放到:

物理内存

10000   ← 进程起始位置
...
10500   ← 逻辑地址 500 对应这里

于是最朴素的转换就是:

三、静态重定位和动态重定位

这部分很容易和“分页地址转换”混淆。

1. 静态重定位

装入程序时一次性完成地址修改。

例如进程程序中:

进程装入物理地址:

装入时直接把地址改成:

以后程序里直接用 10500。

问题是:一旦改完,这个程序就不方便再移动。

因此静态重定位要求:

  • 装入前获得足够连续空间;
  • 装入以后位置基本固定;
  • 很难进行内存紧凑。

2. 动态重定位

动态重定位并不提前改掉逻辑地址,而是在每次执行到该地址时再转换。

需要一个重定位寄存器:

重定位寄存器 = 10000
逻辑地址     = 500
                ↓
物理地址     = 10500

即:

其中:

  • :Physical Address;
  • :Base Address;
  • :Logical Address。

这样,如果操作系统以后把整个进程移动到 20000:

重定位寄存器 = 20000

程序自己的地址仍然是 500:

程序自己完全不知道被移动过。

所以紧凑技术要求动态重定位支持。资料也专门指出:动态运行时装入支持程序移动,使操作系统可以进行紧凑。

四、内存保护:基址寄存器 + 界限寄存器

这是一个非常漂亮的设计。

假设:

进程被分配:
物理起点 = 10000
长度     = 4000

设置:

重定位寄存器 = 10000
界地址寄存器 = 4000

进程发出逻辑地址 。

首先:

如果不是:

越界!

如果合法:

所以可以记:

界限寄存器负责“比”,重定位寄存器负责“加”。

资料也明确这样总结,而且这些寄存器只能由操作系统用特权指令修改,否则用户程序自己修改基址就能访问别人的内存。

五、内存共享

不同进程的逻辑地址空间彼此独立,并不意味着物理内存绝不能重合。

比如:

进程 A 页表 ─┐
              ├──→ 同一组物理代码页
进程 B 页表 ─┘

尤其是只读代码非常适合共享。

资料中的例子是 40 个用户同时运行一个:

  • 代码区:160KB
  • 数据区:40KB

如果完全不共享:

如果共享代码:

节省非常明显。允许多个进程同时访问、但不允许修改的代码称为可重入代码/纯代码;每个进程仍保留自己的私有数据区。

六、连续分配管理

现在进入真正的“怎么给进程分内存”。

连续分配的共同特点:

一个进程占据一整块连续的物理内存。

资料依次介绍:

单一连续
   ↓
固定分区
   ↓
动态分区
   ↓
发现连续分配还是有碎片
   ↓
分页

这是技术演进逻辑,不要死背。

七、单一连续分配

内存只有:

┌───────────────┐
│ 操作系统       │
├───────────────┤
│ 一个用户进程   │
│               │
│               │
└───────────────┘

优点:极其简单。

缺点:

  • 只能运行一道用户程序;
  • 内存利用率低;
  • 大量空间可能浪费。

了解即可。

八、固定分区分配

提前把内存切成几块:

┌──────────────┐
│ OS           │
├──────────────┤
│ 100MB        │
├──────────────┤
│ 200MB        │
├──────────────┤
│ 300MB        │
└──────────────┘

假设一个 120MB 程序进入 200MB 分区:

200MB 分区
┌──────────────┐
│ 120MB 程序   │
├──────────────┤
│ 80MB 浪费    │ ← 内部碎片
└──────────────┘

高频概念:内部碎片

所谓内部碎片:

已经分配给进程的区域内部,却存在没有被有效使用的空间。

固定分区会产生内部碎片。

九、动态分区分配 ★★★★

这块选择题非常爱考。

进程来多少,就给多少连续空间。

例如开始:

1000MB 空闲

依次来:

A 200MB
B 300MB
C 100MB

形成:

| A200 | B300 | C100 | 空闲400 |

后来 B 退出:

| A200 | 空闲300 | C100 | 空闲400 |

现在总空闲:

如果来了一个 500MB 进程,能装吗?

不能。

因为最大的连续空闲区只有 400MB。

这就是:

外部碎片

空闲空间总量够,但被分割成许多不连续的小块。

动态分区主要产生外部碎片。

十、内部碎片和外部碎片一定分清

这是 408 高频陷阱。

管理方式内部碎片外部碎片
单一连续有无
固定分区有无
动态分区通常无有
分页有无
分段通常无有
段页式有无(分页层面)

分页为什么还有内部碎片?

因为最后一页可能没装满。

例如页大小 4KB,一个进程需要 10KB:

第0页 4KB
第1页 4KB
第2页 2KB + 浪费2KB

这 2KB 就是页内的内部碎片。

资料明确指出分页消除了外部碎片,但仍可能存在内部碎片,并指出平均页内碎片约半页。

十一、动态分区的四种分配算法 ★★★★

资料的考点追踪显示,动态分区算法特点在 2019、2024 等年份被考查,最佳适应的分配过程也曾直接出题。

一定掌握:

算法怎么找空闲链组织
First Fit第一个够大的地址递增
Next Fit从上次位置继续找地址循环
Best Fit最小但够大的容量递增
Worst Fit最大的容量递减

1. 首次适应 First Fit

假设:

100  500  200  400

申请:

180

从头找:

  • 100 不够;
  • 500 够;

于是切 500。

特点:

看到第一个能装下的就用。

综合性能通常比较好。

2. 邻近适应 Next Fit

和 First Fit 类似,但不是每次从头开始。

假设上次在:

          ↓
100 500 200 400

下次从这里继续找,再循环一圈。

优点:避免总扫描低地址。

缺点:可能把各处的大块逐渐切碎。

3. 最佳适应 Best Fit

申请 180:

空闲块:
100, 200, 400, 500

能满足的是:

200,400,500

选最小的:

200

剩:

问题恰恰就在这:

经常产生大量非常小、以后谁也用不了的碎片。

所以:

Best Fit 名字叫“最佳”,实际不代表综合性能最佳。

4. 最坏适应 Worst Fit

仍然申请 180:

100,200,400,500

选最大的:

500

剩:

它想避免制造很小的碎片。

但缺点是:

一直切最大的空闲块,很快系统里就没有真正的大块空间了。

十二、动态分区算法题怎么做?

资料中有一道典型题:

空闲分区:

100, 450, 250, 200, 600 KB

依次申请:

212, 417, 112, 426 KB

问哪种算法能全部完成。

对于 Best Fit:

剩:

然后:

剩:

然后:

剩:

最后:

剩:

全部成功,因此题中答案为最佳适应。

做这种题不要凭感觉

一定维护当前空闲表:

原空闲表
→ 第一次分配
→ 更新
→ 第二次
→ 更新
...

尤其:

  • First Fit 看地址顺序;
  • Best Fit 看大小顺序;
  • Next Fit 记住上次查找位置。

十三、动态分区回收的四种情况

回收一个区域 ,只看它前后。

情况 1:前面空闲

[前空闲][X]

合并。

情况 2:后面空闲

[X][后空闲]

合并。

情况 3:前后都空闲

[前空闲][X][后空闲]

三个合成一个。

情况 4:前后都不是空闲

新建一个空闲分区表项。

这正是资料总结的动态分区回收四种情况。

十四、紧凑

假设:

A | 空100 | B | 空200 | C | 空300

总空闲:

但没有一个 600 的连续块。

把进程移动:

A | B | C | 空600

这就叫紧凑。

但是移动以后:

B 原来地址 ≠ B 现在地址
C 原来地址 ≠ C 现在地址

所以必须有动态重定位。

这是非常经典的因果链:

外部碎片
   ↓
紧凑
   ↓
移动进程
   ↓
物理地址发生变化
   ↓
必须动态重定位

十五、分页存储管理 ★★★★★

这是整个第三章最重要的部分之一。

连续分配的核心问题:

一个进程必须找到一块足够大的连续空间。

那为什么一定要求连续?

把一个进程切成小块不就行了?

于是产生分页。

十六、页、页面、页框

假设页面大小:

进程:

逻辑页0
逻辑页1
逻辑页2
逻辑页3

物理内存也切成 4KB 一块:

页框0
页框1
页框2
页框3
页框4
...

然后:

逻辑页0 → 页框6
逻辑页1 → 页框2
逻辑页2 → 页框10
逻辑页3 → 页框3

发现了吗?

进程的页:

0 1 2 3

逻辑上连续。

但物理上可以:

6 2 10 3

完全不连续。

这正是分页最重要的意义:

消除对连续物理空间的要求,从而消除外部碎片。

十七、页表到底是什么? ★★★★★

既然:

页0 → 框6
页1 → 框2
页2 → 框10
页3 → 框3

CPU 怎么知道?

维护一张表:

页号页框号
06
12
210
33

这就是页表。

这里有一个常见坑:

页号通常是页表中的索引,因此页表项不需要再显式存一个页号。

也就是说页表实际上更像:

page_table[0] = 6
page_table[1] = 2
page_table[2] = 10
page_table[3] = 3

十八、分页地址转换 ★★★★★

这是必会计算。

假设页面大小为 ,逻辑地址为 。

首先拆成:

其中:

  • :页号;
  • :页内偏移。

然后页表:

其中 是页框号。

最后:

这就是分页地址转换最核心的公式。

十九、为什么页大小通常是 ?

假设页面大小:

那么地址最低 12 位天然就是页内偏移。

一个 32 位逻辑地址:

┌────────────────────┬────────────┐
│       页号          │ 页内偏移   │
│      20bit          │ 12bit      │
└────────────────────┴────────────┘

根本不用真的执行除法。

直接:

高20位 → 页号
低12位 → 页内偏移

硬件处理非常方便。

二十、分页地址转换完整过程

假设:

逻辑地址 A

CPU 做:

A
│
├── 页号 P
│      │
│      ↓
│    查页表
│      │
│      ↓
│    页框号 b
│
└── 页内偏移 W
        │
        ↓
   拼接 / 计算
        ↓
     物理地址

数学表达:

查到:

于是:

注意:

页内偏移在地址转换过程中不变。

改变的是:

逻辑页号 → 物理页框号

二十一、页表项地址怎么求?

假设:

  • 页表起始物理地址 ;
  • 页号 ;
  • 每个页表项大小 。

第 个页表项地址:

这个和数组寻址完全一样。

所以很多 408 页表综合题本质是:

逻辑地址
→ 页号
→ 页表项地址
→ 页框号
→ 物理地址

二十二、页表项到底需要多大?

资料给出了典型思路。

假如:

  • 物理地址空间 32 位;
  • 页面 4KB。

物理内存页框数:

所以至少需要:

才能表示页框号。

但一个页表项不仅要有页框号,还可能有:

有效位
访问位
修改位
权限位
...

再考虑存储对齐,实际可能采用 4B 页表项。

二十三、为什么普通页表会让访存慢一倍?

假如页表存在内存。

CPU 想读取变量 x。

不能直接访问 x,因为手里只有虚拟地址。

于是:

第一次访问内存:
读页表 → 得到页框号

第二次访问内存:
真正访问 x

所以一次逻辑访存变成至少两次物理访存。

怎么办?

TLB。

二十四、TLB / 快表 ★★★★★

TLB 本质:

页表项的高速缓存。

完整页表在主存:

慢表

近期常用页表项放 TLB:

快表

因为程序存在局部性:

刚访问页5
↓
接下来很可能继续访问页5

于是把:

页5 → 页框17

缓存起来。

资料明确指出 TLB 是具有并行查找能力的高速缓存,用于保存当前活跃的若干页表项。

二十五、TLB 命中与不命中

命中

虚拟地址
   ↓
TLB
   ↓ 命中
页框号
   ↓
访问真正数据

无需读取主存中的页表。

TLB 未命中

虚拟地址
  ↓
TLB miss
  ↓
主存页表
  ↓
找到页框号
  ↓
更新TLB
  ↓
访问数据

非常重要:

TLB 未命中 ≠ 缺页。

这是最容易混淆的东西之一。

TLB miss 只是:

快表里没有这个页表项。

但是页可能完全就在主存中。

二十六、TLB、页表、缺页三者关系

可以这样记:

TLB
↓
“最近映射关系在不在高速缓存?”

页表状态位
↓
“页面在不在物理内存?”

缺页
↓
“不在内存,需要从外存调进来。”

因此:

TLB 命中

一定意味着对应页已经在内存。

TLB 未命中

有两种:

页在内存   → 普通 TLB miss
页不在内存 → 缺页

二十七、TLB 有效访问时间计算 ★★★★★

这种题不要背死公式,因为题目可能规定:

  • TLB 和内存串行查找;
  • TLB 和页表同时查找。

要根据题干画流程。

资料中有一道题规定:

  • TLB:1ns;
  • 内存:100ns;
  • TLB 与页表同时查找;
  • 命中率 。

命中:

未命中,由于页表查询和 TLB 同时进行,到 100ns 已经获得页框号,再访问一次数据:

所以:

要求:

则:

资料题答案就是约 。

核心技巧

不要先写公式。

先写:

命中路径花多久?
未命中路径花多久?

再求期望:

二十八、多级页表 ★★★★

为什么需要?

假设:

  • 32 位虚拟地址;
  • 页大小 4KB;
  • 页表项 4B。

虚拟页数:

页表大小:

每个进程都可能需要一个很大的连续页表。

但现实中进程可能只用了地址空间的一小部分。

例如:

4GB 虚拟空间

低地址:程序
中间:大量未使用
高地址:栈

为什么为没用到的区域准备页表项?

于是把页表本身再分页。

二十九、二级页表怎么理解?

逻辑地址:

┌────────┬────────┬────────────┐
│一级页号│二级页号│ 页内偏移   │
└────────┴────────┴────────────┘

过程:

一级页号
  ↓
一级页表
  ↓
找到某张二级页表在哪
  ↓
二级页号
  ↓
二级页表
  ↓
找到数据页框
  ↓
页内偏移
  ↓
物理地址

所以:

一级页表项保存的是对应二级页表所在的物理页框号。

这是配套模拟题直接考过的点。

三十、多级页表解决了什么,又付出了什么?

这是资料直接要求思考的问题。

优点

不用一次建立巨大单级页表。

只为实际使用的地址区建立下级页表。

所以:

节省页表占用的内存。

缺点

访问次数增加。

二级页表、没有 TLB:

1. 查一级页表
2. 查二级页表
3. 访问真正数据

需要 3 次主存访问。

所以资料总结:

多级页表以更复杂、更慢的地址转换,换取更低的页表空间开销。

三十一、分段存储管理 ★★★

分页是从计算机管理内存角度出发。

分段则从程序员看到的程序逻辑结构出发。

一个程序天然包含:

主程序
函数A
函数B
全局数据
栈
...

于是:

段0 主程序
段1 函数A
段2 数据
段3 栈

不同段长度可以不同。

三十二、分段逻辑地址

分段地址不是单纯一个连续的线性数,而由:

段号 S
+
段内偏移 W

组成。

即:

例如:

(3, 500)

意思是:

第 3 段中的第 500 个字节。

资料明确将分段地址称为二维地址,并强调段号和段内偏移共同定位一个地址。

三十三、段表

例如:

段号段基址段长
0100003000
1300001000
2500006000

访问:

先检查:

合法。

再计算:

三十四、为什么段表必须保存“段长”?

因为不同段大小不同:

段0:3000
段1:1000
段2:6000

如果访问:

虽然:

能算出来,但其实已经越出第 1 段。

所以先必须检查:

此外还要检查:

资料将这两步称为分段系统的地址越界保护。

三十五、分页和分段的核心区别 ★★★★

分页分段
划分依据系统固定划分程序逻辑结构
单位大小固定可变
对程序员基本透明有逻辑意义
地址页号+页内偏移段号+段内偏移
外部碎片无有
内部碎片有通常无
共享保护可以更自然
每个逻辑单元无明确语义代码段、数据段等

一句话:

分页解决“内存怎么高效装”;分段解决“程序怎么按逻辑组织”。

三十六、段页式存储管理 ★★★

那能不能:

程序员看起来按段
+
物理内存实际上按页

当然可以。

这就是段页式。

先分段:

段0
段1
段2

每段再分页:

段0:
页0 页1 页2

段1:
页0 页1

于是逻辑地址:

┌──────┬──────┬──────────┐
│ 段号 │ 页号 │ 页内偏移 │
└──────┴──────┴──────────┘

即:

三十七、段页式地址转换

这是重点理解过程。

每个进程:

一张段表

每个段:

一张页表

所以:

逻辑地址 (S,P,W)
       ↓
段号 S
       ↓
段表
       ↓
找到该段页表的位置
       ↓
页号 P
       ↓
该段页表
       ↓
页框号 b
       ↓
加/拼接 W
       ↓
物理地址

一定记:

一个进程一张段表;一个段一张页表,所以一个进程可能有多张页表。

无 TLB 时:

访问段表
访问页表
访问真正数据

通常需要 3 次主存访问。

三十八、学习页式、段式、段页式只抓三件事

资料自己给出了一个非常好的复习方法:

对三种管理方式,只需要牢牢掌握:

  1. 逻辑地址结构
  2. 页表/段表表项结构
  3. 寻址过程

例如:

类型地址结构表项寻址
分页页框号页表→页框
分段基址+段长段表→基址
段页段表→页表段表→页表→页框

这样比死背几十句话有效得多。

三十九、为什么要虚拟内存? ★★★★★

前面基本分页虽然允许:

进程物理上不连续

但是传统管理仍有两个限制。

资料称为:

一次性

程序必须全部装进内存才能运行。

驻留性

程序一旦装进去,运行期间一直留在内存。

问题:

一个 10GB 程序,而物理内存只剩 5GB:

传统基本分页:
装不进去
→ 不能运行

但程序真的会在某一个瞬间同时使用全部 10GB 内容吗?

通常不会。

所以产生虚拟内存。

四十、虚拟内存的理论基础:局部性原理 ★★★★★

1. 时间局部性

刚访问过的东西,近期很可能再次访问。

例如:

for (...)
    sum += a;

变量:

sum
a
循环控制变量

会反复访问。

2. 空间局部性

访问某个地址以后,很可能访问附近地址。

例如:

for (i=0;i<n;i++)
    sum += a[i];

数组:

a[0],a[1],a[2],a[3]...

连续存放。

因此访问 a[0] 后,很可能访问附近的 a[1]。

资料明确将时间局部性、空间局部性作为虚拟内存的理论基础。

四十一、虚拟内存真正的思想

既然程序在某段时间只集中使用少量页面:

只把现在需要的页面放进内存。

例如进程有:

1000页

当前只使用:

5,6,7,20

那么内存中可能只放:

5 6 7 20

剩余 996 页:

留在外存

如果以后访问第 300 页:

发现不在内存
↓
把第300页从磁盘调入
↓
继续运行

这就是请求分页。

四十二、虚拟地址空间不一定大于物理空间

408 很喜欢这类语言陷阱。

错误说法:

“虚拟地址空间总是大于物理地址空间。”

不是。

虚拟地址空间最大大小主要取决于:

虚拟地址位数

物理地址空间取决于:

物理地址位数

二者没有“虚拟一定大于物理”的数学必然关系。配套题也专门把这一说法列为错误项。

实际虚拟内存技术允许:

一个进程的虚拟地址空间可以大于当前实际物理内存。

“可以”不等于“必须”。

四十三、请求分页系统 ★★★★★

基本分页页表可能只是:

页号 → 页框号

请求分页需要知道更多信息:

字段作用
页框号当前在哪个页框
状态位 页面在不在内存
访问字段 最近访问情况
修改位 是否被修改
外存地址页面在磁盘哪里

资料中特别列出这几个字段。

四十四、状态位 P

例如:

页号页框P
051
1—0
281

访问页 2:

P=1
→ 在内存

访问页 1:

P=0
→ 不在内存
→ 缺页异常

四十五、修改位 M 为什么重要?

假如一个页面从磁盘读进来以后:

从未修改

现在要淘汰。

磁盘上原来的内容和内存里完全相同。

直接扔掉即可:

不写磁盘

如果页面被改过:

内存内容 ≠ 磁盘内容

淘汰前必须:

写回磁盘

所以:

修改过的页比干净页更“贵”。

这直接导致后面的改进 CLOCK 算法。

四十六、缺页异常完整处理流程 ★★★★★

这是本章必须真正理解的过程。资料的考点追踪显示缺页处理在 2011、2013、2014、2020、2022 等年份被多次考查。

CPU 访问逻辑地址:

          虚拟地址
              ↓
            TLB
       ┌──────┴──────┐
     命中           未命中
      ↓                ↓
得到页框            查页表
                       ↓
                    P = ?
                 ┌─────┴─────┐
               P=1          P=0
                ↓             ↓
             得页框        缺页异常
                              ↓
                        操作系统接管
                              ↓
                    内存是否有空闲页框?
                      ┌──────┴──────┐
                     有             无
                     ↓              ↓
                   直接用         选牺牲页
                                    ↓
                              脏页要写回
                                    ↓
                            调入目标页面
                                    ↓
                             修改页表/TLB
                                    ↓
                            重新执行访存

四十七、缺页不是普通的函数调用

缺页是由硬件发现:

P=0

然后触发异常。

此时:

用户程序
   ↓
异常
   ↓
内核处理
   ↓
磁盘 I/O
   ↓
页面装入
   ↓
重新执行导致缺页的指令/地址转换

资料特别指出两点:

1. 缺页发生在指令执行期间

并不是一定等到指令执行结束以后。

2. 一条指令可能多次缺页

例如某条复杂指令:

指令本身跨2页
源操作数跨2页
目的操作数跨2页

最多可能涉及:

个页面,因此可能多次触发缺页。

四十八、三个异常情况一定区分

情况一:TLB miss

TLB 里没有
页在内存

正常查页表即可。

情况二:Page Fault

页表存在这个页
但 P=0

页面合法,只是当前没在内存。

需要从磁盘调入。

情况三:地址越界

例如进程总共只有 10 页,却访问:

第20页

这是:

非法地址访问

不是“把第20页从磁盘调进来”。

因为这个页压根不属于该进程合法地址空间。

四十九、页框、驻留集与最小页框数 ★★★★

驻留集

一个进程当前占用的所有物理页框集合。

例如:

进程P驻留集:
{3, 7, 10, 18}

驻留集大小:

为什么不能只给一个页框?

假如一条指令:

LOAD X

指令自己在页面 A。

操作数 X 在页面 B。

至少可能需要:

A + B

两个页面。

资料给出的典型结论是:

单地址 + 直接寻址

至少 2 页框:

指令
数据

一级间接寻址

至少 3 页框:

指令
指针
数据

复杂情况下,一条指令和两个操作数各跨两个页面:

最少可能需要 6 个页框。

五十、驻留集是不是越大越好?

不是。

驻留集太小:

频繁缺页
↓
频繁磁盘IO
↓
性能暴跌

驻留集太大:

这个进程占很多页框
↓
别的进程页框减少
↓
系统并发进程数降低

所以是一个平衡问题。

五十一、三种页框分配/置换策略 ★★★

资料指出 2015 年统考直接考过名称。

1. 固定分配 + 局部置换

给进程 P:

4 个页框

运行过程中始终 4 个。

缺页时:

只能淘汰 P 自己的页面。

2. 可变分配 + 全局置换

P 缺页:

有空闲框 → 再给 P 一个

没有空闲框
→ 可以从整个系统所有进程的页框中选一个淘汰

所以 P 的驻留集可能变大或变小。

3. 可变分配 + 局部置换

正常缺页时先在自己里面替换。

但系统还会根据缺页率调整:

缺页率过高 → 多给一些框
缺页率过低 → 回收一些框

资料还特别提醒:

“固定分配 + 全局置换”这一组合在这里不成立,因为全局置换可能改变某进程拥有的页框数量,与“固定”矛盾。

五十二、页面置换算法 ★★★★★

这是和分页地址转换并列的超级重点。

当:

发生缺页
+
没有空闲页框

必须问:

淘汰谁?

这就是页面置换算法。

四大算法:

OPT
FIFO
LRU
CLOCK

五十三、OPT 最佳置换算法

思想:

淘汰“未来最长时间不会被访问”的页面。

例如当前:

内存:A B C

从现在往未来看:

A 2步以后访问
B 100步以后访问
C 5步以后访问

那么淘汰:

B

因为未来最久才用它。

OPT 为什么最好?

理论上每一步都作出最理想选择,所以缺页率最低。

为什么实际不能用?

因为:

操作系统不知道未来的页面访问序列。

所以它主要用于:

作为理论最优基准,评价其他算法。

资料中的典型序列配 3 个页框,OPT 共发生 9 次缺页。

五十四、FIFO 先进先出 ★★★★

思想:

谁最早进入内存,就淘汰谁。

例如:

进入顺序:
A → B → C

缺页后:

淘汰 A

简单。

但最大问题:

最早进来
≠
最近没用

A 虽然来得早,但可能是一个一直频繁访问的热点页。

FIFO 完全不管。

五十五、FIFO 的超级考点:Belady 异常

正常直觉:

页框越多,缺页应该越少。

FIFO 却可能出现:

页框更多
↓
缺页反而更多

资料例子:

页面序列:

3,2,1,0,3,2,4,3,2,1,0,4

3 个页框:

次缺页。

4 个页框:

次缺页。

这就是 Belady 异常。

在教材所重点比较的 FIFO、LRU、OPT 中:

  • FIFO 可能;
  • LRU 不会;
  • OPT 不会。

五十六、LRU 最近最久未使用 ★★★★★

全称:

Least Recently Used

思想:

淘汰“过去最长时间没有访问”的页面。

注意与 OPT 的区别。

OPT

看:

未来

谁未来最晚用。

LRU

看:

过去

谁已经最久没用。

资料专门强调:

OPT 面向未来,而 LRU 根据过去的访问历史进行判断。

五十七、LRU 怎么手算?

当前内存:

1 2 3

现在访问:

4

缺页。

向访问序列左边倒着看:

谁最近一次出现离当前位置最远?

假设:

... 2 ... 1 ........ 3 ... | 4

最近:

3 最近
1 次之
2 最久

淘汰:

2

所以手算 LRU 不一定要真的维护计时器。

口诀:

缺页时向左找,最后出现最远的淘汰。

五十八、FIFO 与 LRU 手算最容易错在哪里?

假设命中一个已经在内存中的页面。

LRU

它的“最近使用时间”必须更新。

FIFO

进入内存的时间不变。

例如:

FIFO 队列:
A B C

访问 A 命中。

FIFO 顺序还是:

A B C

不是:

B C A

因为 FIFO 看:

什么时候进入内存。

LRU 才看:

什么时候最近被访问。

五十九、资料中的典型置换题

访问序列:

5,4,3,2,4,3,1,4,3,2,1,5

3 个页框。

资料给出的计算结果:

做这类题建议画:

访问框1框2框3缺页?
55✓
454✓
3543✓
...

千万不要纯心算。

六十、缺页率怎么算?

定义非常直接:

例如资料中的 LRU 题:

页面序列一共:

次访问。

3 页框时缺页:

次:

4 页框时缺页:

次:

注意:

一开始空页框时把第一页装进去,同样算缺页。

但通常:

初始装入是否算“页面置换”则不是一回事。

初始缺页:

缺页 ✓
淘汰旧页 ✗

六十一、CLOCK 时钟算法 ★★★★★

LRU 效果很好,但精确实现 LRU 成本较高。

所以设计一个便宜的近似算法:

CLOCK / Second Chance。

每页增加一个访问位:

访问页面:

所有页框组成环:

     A
   ↗   ↘
  D     B
   ↖   ↙
     C

有一个时钟指针。

六十二、简单 CLOCK 算法过程

发生缺页需要淘汰时:

指针指向

直接淘汰。

指针指向

说明最近访问过:

A = 1 → 改成 0

然后:

指针向后走

相当于:

再给它一次机会。

所以又叫 Second Chance。

资料的具体规则也是:

六十三、CLOCK 为什么近似 LRU?

LRU 精确问:

谁已经最久没访问?

CLOCK 不知道精确时间,只知道:

最近访问过? 1
最近没访问? 0

于是优先淘汰:

0

相当于用 1 bit 的粗略历史逼近 LRU。

六十四、改进型 CLOCK ★★★★★

简单 CLOCK 只考虑:

但是前面说了:

脏页淘汰比干净页贵。

所以再加:

得到:

四类:

类别含义淘汰优先级
100没访问、没修改最优
201没访问、修改过次优
310最近访问、干净较差
411最近访问、脏最差

记忆:

先“不访问”,再“不修改”。

因为:

  • :预计短期不太会再用;
  • :淘汰不用写磁盘。

六十五、改进 CLOCK 扫描过程

资料给出的逻辑是:

第一轮寻找:

找到马上淘汰。

如果没有,再寻找:

并在这一过程中把经过页面的 清零。

之后再重新寻找:

再没有就找:

最终一定能选出来。

六十六、四大页面置换算法总结

算法看什么优点缺点
OPT未来理论最优不可实现
FIFO进入时间最简单性能差、可能 Belady
LRU过去访问时间接近 OPT实现开销大
CLOCK访问位性价比好只是近似 LRU
改进 CLOCK访问位+修改位兼顾使用与 I/O扫描更复杂

从 408 复习角度:

一定会做 FIFO、LRU、CLOCK 的过程题。

六十七、抖动 / 颠簸

这是理解型内容。

假设一个进程正常运行至少需要经常使用:

A B C D

但只给它两个页框。

于是:

访问A → 调入A
访问B → 调入B
访问C → 淘汰A
访问D → 淘汰B
访问A → 又把A调回来
访问B → 又把B调回来
...

CPU 大量时间不再计算,而是在:

缺页
读磁盘
写磁盘
换页

这叫抖动。

资料定义为:

刚换出去的页面很快又要换入,刚换入的页面又很快被换出,根本原因是进程拥有的物理块过少。

六十八、工作集怎么理解?

这一节在资料目录中标有 *,所以对于 408 主线复习,理解思想即可。

可以把工作集简单理解成:

进程最近一段时间真正活跃、经常使用的那些页面。

例如最近运行阶段主要反复访问:

1,2,4,5

那么这几个页面就是当前活跃集合的核心。

如果给它的页框连这些活跃页都容不下:

频繁互相替换
↓
抖动

所以其思想本质是:

至少让进程当前真正需要的一组页面比较稳定地驻留内存。

六十九、页面缓冲算法与页框回收

这一块不是前面地址转换、页面置换那么核心,但理解系统为什么这样设计很有帮助。

一个页面被选中“淘汰”,不一定马上彻底丢掉。

系统可以维护:

空闲页面链表
修改页面链表

干净页

如果页面没修改:

先放进空闲页框链表

数据甚至可以暂时保留。

如果刚淘汰,马上又有人需要它:

直接捡回来

不用读磁盘。

脏页

如果页面已经修改:

先放修改页面链表

不要每淘汰一页就立即进行一次磁盘写。

可以积累一批后:

批量写盘

减少磁盘 I/O 次数。资料明确指出页面缓冲算法通过这两类链表降低换入换出频率,并让简单 FIFO 等算法也能获得较好的实际表现。

七十、内存映射文件 mmap ★★★

这是近年需要重视的新考点,资料标记为 2025 考点。

传统文件访问:

read(...)
write(...)

内存映射则可以把:

磁盘文件

映射到:

进程虚拟地址空间的一段区域

之后:

p[i]

表面上像读内存。

底层:

第一次访问对应页
↓
可能缺页
↓
OS从文件调入

程序无需自己关心具体磁盘读操作。

七十一、mmap 与共享内存

假设进程 A:

虚拟页 100

进程 B:

虚拟页 500

可以让:

A 虚页100 ─┐
            ├──→ 同一物理页框
B 虚页500 ─┘

于是 A 修改以后,B 访问相同共享映射可看到对应内容。

因此 mmap 两个主要作用:

  1. 文件像内存一样访问;
  2. 方便多个进程共享同一文件/物理页。

七十二、虚拟存储器性能受什么影响? ★★★★

资料明确标记这一部分在 2020、2022 被考查。核心指标:

缺页率。

影响缺页率和总体性能的几个因素:

页面大小
页框数量
页面置换算法
页面写回策略
程序局部性

七十三、页面大小是不是越大越好?

不是。

页面小

优点:

内部碎片小

缺点:

页数多
→ 页表大
→ 管理开销大
→ 更多页表项

页面大

优点:

页数少
→ 页表小
→ 一次磁盘I/O传更多相邻数据

缺点:

页内碎片大

所以页面大小是权衡。

七十四、页框数是不是越多越好?

对某单个进程:

页框增加
→ 通常缺页率下降

但达到某个程度后:

再增加
→ 缺页率改善已经很小

与此同时:

你多占一个页框
→ 其他进程少一个页框
→ 系统并发度降低

所以不是无限多越好。

七十五、程序局部性为什么能极大影响缺页率?

例如二维数组按行存储:

int a[128][128];

程序 A:

for (j=0; j<128; j++)
    for (i=0; i<128; i++)
        use(a[i][j]);

按列访问。

内存实际却:

a[0][0] a[0][1] ... a[0][127]
a[1][0] a[1][1] ...

连续访问经常跨到不同页面。

如果改成按行:

for (i=0; i<128; i++)
    for (j=0; j<128; j++)
        use(a[i][j]);

一旦把包含当前行的页调进来,可以连续访问大量元素。

配套资料中的类似题甚至出现:

级别的页故障与约

次页故障的巨大差别。

所以:

程序的数据访问顺序本身就是虚拟内存性能的重要因素。

七十六、本章最容易混的十组概念

1. 页 vs 页框

页:进程逻辑地址空间
页框:物理内存

大小相同。

2. 页号 vs 页框号

页号:虚拟
页框号:物理

页表完成:

页号 → 页框号

3. TLB miss vs page fault

TLB miss:
映射不在快表

Page fault:
页面不在物理内存

4. 内部碎片 vs 外部碎片

内部:
分给我的空间里面浪费

外部:
空闲空间散在分区之间

5. 页表 vs TLB

页表:
完整映射

TLB:
近期页表项缓存

6. 基本分页 vs 请求分页

基本分页:

运行前把全部页面装入

请求分页:

需要哪个页面再装哪个

7. 分页 vs 分段

分页:

固定大小
系统管理
提高物理内存利用率

分段:

可变大小
反映程序逻辑
利于保护共享

8. OPT vs LRU

OPT:看未来
LRU:看过去

9. FIFO vs LRU 命中后的处理

FIFO:

命中不改变进入顺序

LRU:

命中意味着它刚刚被使用,历史必须更新

10. 缺页次数 vs 页面置换次数

初始空框:

访问新页
→ 缺页
→ 直接装入

有:

缺页

但是没有:

页面置换

所以:

七十七、408 做页表综合题的固定流程 ★★★★★

遇到任何页表、TLB、Cache、请求分页混合题,建议严格按照这个顺序。

Step 1:先拆虚拟地址

页面大小:

则:

低 k 位 = 页内偏移
其余高位 = 虚页号

Step 2:先问 TLB

TLB hit?

是:

直接获得页框

否:

访问页表

Step 3:检查页表有效/存在位

页面在内存。

缺页。

Step 4:缺页时看有没有空闲页框

有:

直接装入

没有:

执行页面置换算法

Step 5:若淘汰页脏

需要写回磁盘。

Step 6:调入所需页

更新:

页表
TLB
访问位
修改位

Step 7:重新执行导致缺页的访问

最终:

这套流程基本可以覆盖本章最重要的综合计算题。

七十八、408 页面置换题固定流程 ★★★★★

给:

页面序列
页框数
算法

第一步画表。

例如:

序列:
1 2 3 4 2 1 ...

3 页框:

访问框1框2框3缺页
1
2
3
4

然后:

FIFO

记录:

最早进入内存的是谁。

LRU

记录:

最近最久没有使用的是谁。

OPT

看:

后面谁最晚出现。

CLOCK

维护:

访问位
+
时钟指针

不要跳步骤。

七十九、本章重要程度排序

结合资料中的考点追踪,建议按下面优先级复习。

级别内容
S分页地址结构与虚实地址转换
S页表、TLB、多级页表
S请求分页、缺页异常处理
SFIFO、LRU、OPT、CLOCK
A动态分区 FF/NF/BF/WF
A页框分配、驻留集、缺页率
A内部/外部碎片
B分段、段页式
B虚拟内存局部性
Bmmap
C页面缓冲、页框回收、抖动/工作集

其中从资料中的考点追踪看:

  • 动态分区算法:2019、2024 等;
  • TLB 性能:2009;
  • 缺页处理:2011、2013、2014、2020、2022;
  • LRU:2009、2015、2019、2025;
  • CLOCK:2010、2012 等;
  • 页框分配策略:2015;
  • 最小页框数:2025;
  • 内存映射文件:2025;
  • 虚拟存储性能:2020、2022。

八十、最后把第三章压缩成一条知识链

你如果能把下面这条链完全讲出来,第三章主干基本就通了:

多道程序共享内存
        ↓
必须分配、保护、地址转换
        ↓
连续分配
        ↓
固定分区 → 内部碎片
动态分区 → 外部碎片
        ↓
分页
        ↓
页 / 页框 / 页表
        ↓
虚拟地址
[页号 | 页内偏移]
        ↓
页号查页表
        ↓
页框号
        ↓
物理地址
        ↓
普通页表太慢
        ↓
TLB
        ↓
普通页表太大
        ↓
多级页表
        ↓
程序逻辑结构又不明显
        ↓
分段 / 段页式
        ↓
但传统分页仍要求全部装入
        ↓
局部性原理
        ↓
虚拟内存
        ↓
请求分页
        ↓
页面不在内存
        ↓
缺页异常
        ↓
有空闲框 → 调入
无空闲框 → 页面置换
        ↓
OPT / FIFO / LRU / CLOCK
        ↓
缺页过多 → 抖动
        ↓
调整驻留集、改善局部性、
选择更好的置换/写回策略

最需要真正做到“看到题就会做”的,是这四套流程:

这四块掌握以后,第 3 章的大多数选择题和综合分析题就有了稳定的解题框架。

On this page

一、先搞懂:为什么操作系统一定要管理内存?二、逻辑地址、物理地址与地址重定位1. 物理地址2. 逻辑地址三、静态重定位和动态重定位1. 静态重定位2. 动态重定位四、内存保护:基址寄存器 + 界限寄存器五、内存共享六、连续分配管理七、单一连续分配八、固定分区分配高频概念:内部碎片九、动态分区分配 ★★★★外部碎片十、内部碎片和外部碎片一定分清十一、动态分区的四种分配算法 ★★★★1. 首次适应 First Fit2. 邻近适应 Next Fit3. 最佳适应 Best Fit4. 最坏适应 Worst Fit十二、动态分区算法题怎么做?做这种题不要凭感觉十三、动态分区回收的四种情况情况 1:前面空闲情况 2:后面空闲情况 3:前后都空闲情况 4:前后都不是空闲十四、紧凑十五、分页存储管理 ★★★★★十六、页、页面、页框十七、页表到底是什么? ★★★★★十八、分页地址转换 ★★★★★十九、为什么页大小通常是 ?二十、分页地址转换完整过程二十一、页表项地址怎么求?二十二、页表项到底需要多大?二十三、为什么普通页表会让访存慢一倍?TLB。二十四、TLB / 快表 ★★★★★二十五、TLB 命中与不命中命中TLB 未命中二十六、TLB、页表、缺页三者关系TLB 命中TLB 未命中二十七、TLB 有效访问时间计算 ★★★★★核心技巧二十八、多级页表 ★★★★二十九、二级页表怎么理解?三十、多级页表解决了什么,又付出了什么?优点缺点三十一、分段存储管理 ★★★三十二、分段逻辑地址三十三、段表三十四、为什么段表必须保存“段长”?三十五、分页和分段的核心区别 ★★★★三十六、段页式存储管理 ★★★三十七、段页式地址转换三十八、学习页式、段式、段页式只抓三件事三十九、为什么要虚拟内存? ★★★★★一次性驻留性四十、虚拟内存的理论基础:局部性原理 ★★★★★1. 时间局部性2. 空间局部性四十一、虚拟内存真正的思想四十二、虚拟地址空间不一定大于物理空间四十三、请求分页系统 ★★★★★四十四、状态位 P四十五、修改位 M 为什么重要?四十六、缺页异常完整处理流程 ★★★★★四十七、缺页不是普通的函数调用1. 缺页发生在指令执行期间2. 一条指令可能多次缺页四十八、三个异常情况一定区分情况一:TLB miss情况二:Page Fault情况三:地址越界四十九、页框、驻留集与最小页框数 ★★★★驻留集为什么不能只给一个页框?单地址 + 直接寻址一级间接寻址五十、驻留集是不是越大越好?五十一、三种页框分配/置换策略 ★★★1. 固定分配 + 局部置换2. 可变分配 + 全局置换3. 可变分配 + 局部置换五十二、页面置换算法 ★★★★★五十三、OPT 最佳置换算法OPT 为什么最好?为什么实际不能用?五十四、FIFO 先进先出 ★★★★五十五、FIFO 的超级考点:Belady 异常五十六、LRU 最近最久未使用 ★★★★★OPTLRU五十七、LRU 怎么手算?五十八、FIFO 与 LRU 手算最容易错在哪里?LRUFIFO五十九、资料中的典型置换题六十、缺页率怎么算?六十一、CLOCK 时钟算法 ★★★★★六十二、简单 CLOCK 算法过程指针指向 指针指向 六十三、CLOCK 为什么近似 LRU?六十四、改进型 CLOCK ★★★★★六十五、改进 CLOCK 扫描过程六十六、四大页面置换算法总结六十七、抖动 / 颠簸六十八、工作集怎么理解?六十九、页面缓冲算法与页框回收干净页脏页七十、内存映射文件 mmap ★★★七十一、mmap 与共享内存七十二、虚拟存储器性能受什么影响? ★★★★七十三、页面大小是不是越大越好?页面小页面大七十四、页框数是不是越多越好?七十五、程序局部性为什么能极大影响缺页率?七十六、本章最容易混的十组概念1. 页 vs 页框2. 页号 vs 页框号3. TLB miss vs page fault4. 内部碎片 vs 外部碎片5. 页表 vs TLB6. 基本分页 vs 请求分页7. 分页 vs 分段8. OPT vs LRU9. FIFO vs LRU 命中后的处理10. 缺页次数 vs 页面置换次数七十七、408 做页表综合题的固定流程 ★★★★★Step 1:先拆虚拟地址Step 2:先问 TLBStep 3:检查页表有效/存在位Step 4:缺页时看有没有空闲页框Step 5:若淘汰页脏Step 6:调入所需页Step 7:重新执行导致缺页的访问七十八、408 页面置换题固定流程 ★★★★★FIFOLRUOPTCLOCK七十九、本章重要程度排序八十、最后把第三章压缩成一条知识链