请求分页管理方式
请求分页 = 基本分页 + 请求调页 + 页面置换。
它是虚拟存储最常用的实现方式。这一节讲清三件事:页表要多存什么、缺页了怎么办、页从哪儿调进来。
机制
请求页表:多出四个字段
基本分页的页表项只需存页框号。要支持”页可能不在内存”,必须补充信息:
| 页号 | 页框号 | 存在位 P | 访问位 A | 修改位 M | 外存地址 |
|---|
- 存在位 P:该页是否已调入内存。这是缺页判断的唯一依据。
- 访问位 A:该页近期是否被访问过,供置换算法参考。
- 修改位 M(脏位):该页调入内存后是否被修改过。未修改的页被淘汰时不必写回外存,直接丢弃即可——这能省掉一次磁盘写。
- 外存地址:该页在外存中的存放位置,缺页时按它去取。
四个字段各服务于一个目的:P 管”在不在”,A 和 M 管”该淘汰谁”,外存地址管”去哪儿取”。
缺页中断处理流程
访问某页时若发现 P = 0,硬件产生缺页中断:
flowchart TB A["访问逻辑地址,查页表"]:::n B{"存在位 P = 1?"}:::c C["直接访问,结束"]:::ok D["<b>产生缺页中断</b><br/>保护现场,转中断处理程序"]:::warn E{"内存有空闲页框?"}:::c F["<b>置换</b>:按算法选一页淘汰"]:::warn G{"被淘汰页 M = 1?"}:::c H["写回外存"]:::io I["从外存调入所需页"]:::io J["修改页表:P←1,填页框号<br/>并在快表中增加表项"]:::n K["恢复现场,<b>重新执行被中断的指令</b>"]:::ok A --> B B -->|"是"| C B -->|"否"| D --> E E -->|"有"| I E -->|"无"| F --> G G -->|"是"| H --> I G -->|"否"| I I --> J --> K classDef n fill:#f1f5f9,stroke:#94a3b8,color:#334155 classDef c fill:#e0e7ff,stroke:#6366f1,color:#312e81 classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f classDef io fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
流程里有四个必考的点:
① 缺页中断属于”内中断”中的”故障(fault)”。
它是在指令执行期间产生的,由当前指令本身引起,因此属于内中断(异常)而非外中断。而且它是故障类型——故障的特点是可以被修复,修复后重新执行原指令。这与”陷阱(trap,如系统调用,执行下一条指令)“和”终止(abort,不可恢复)“不同。
② 处理完毕后要”重新执行”被中断的那条指令,而不是执行下一条。
因为那条指令根本没执行成功——它要的数据当时不在内存。页调进来之后必须让它从头再来一遍。这是”故障”类异常的共同特征。
③ 一条指令可能产生多次缺页中断。
例如一条指令跨越两个页面存放,其操作数又分别在另外两个页面上,最多可能引发 4 次缺页。这说明缺页中断与”一条指令一次中断”没有必然联系。
④ 只有被淘汰页的 M = 1 时才需要写回。
这正是修改位存在的价值——把”必须写回”变成了”可能不必写回”,缺页处理的平均代价因此显著下降。
调页策略:何时调入
请求调页:只在缺页时才调入所需页面。
- 优点:只调真正用到的页,不浪费。
- 缺点:每次只调一页,缺页中断频繁,I/O 开销大。
预调页:预计将来会用到,提前一并调入。
- 依据是空间局部性——既然访问了某页,附近的页多半也快用到。
- 若预测准确,可大幅减少缺页次数;预测失败则是纯浪费。
疑问点:预调页是否真的由程序员指定页面
教材原文:“预调页策略主要用于进程的首次调入,由程序员指出先调入哪些页。” 程序员真的可以指定吗?
教材这句话描述的是理论模型,实际系统中程序员通常不逐页指定,但确实可以向内核表达调入意图。
分三层说清楚:
理论层(考纲口径):预调页主要用于进程首次调入——此时进程还没运行过,系统没有任何访问历史可供参考,无从预测,所以只能依靠程序员(或编译器/链接器)事先给出提示。这个口径要记住,考试按它答。
实践层:现代系统提供的是”建议”而非”指定”。程序员能做的是告诉内核自己的访问意图,由内核决定要不要照办:
madvise(addr, len, MADV_WILLNEED)—— 告诉内核”这段我马上要用”,内核可提前预读posix_fadvise()—— 对文件访问模式给出提示(顺序/随机/即将使用)mmap()的MAP_POPULATE标志 —— 映射时就把页表填好、把页面预先调入
注意这些都是按”地址区间”给建议,不是按页号逐页点名。
现实层:真正起主要作用的是内核的自动预取。内核会检测访问模式,一旦识别出顺序访问就自动向前多读若干页。这比程序员手工指定有效得多,因为它基于运行时的真实行为。
一句话:“程序员能表达意图”是真的;“逐页指定”在现代系统中不是常规做法。
从何处调入页面
疑问点:三种调入来源的记忆方法与工程现状
教材把外存分为文件区与对换区,并给出三种调入情形。 这部分应当如何记忆?实际工程中是否也这样实现?
先记住那个前提,三种情形就都能推出来:
对换区采用连续分配、文件区采用离散分配,因此对换区的 I/O 速度更快。
于是原则很清楚:能放对换区就放对换区(因为快);放不下的时候才做取舍。 三种情形就是”对换区够不够”的三档:
① 对换区空间充足 → 全部从对换区调入。为此在进程运行前,先把相关文件从文件区复制到对换区。用一次拷贝换取此后每次调页都走快速通道。
② 对换区空间不足 → 按”会不会被修改”分流:
- 不会被修改的(代码段、只读数据):直接从文件区调入。换出时根本不必写回——文件区里那份原本就是最新的,直接丢弃即可。
- 可能被修改的:换出时必须写到对换区,以后从对换区调入。
这一档的记忆抓手是:只读的东西可以留在文件区(反正不用写回),会改的东西必须进对换区(因为要写回,而对换区快)。
③ UNIX 方式 → 按”有没有运行过”分流:
- 未运行过的页:从文件区调入(还没被改过,文件区那份就是原样)
- 运行过又被换出的页:从对换区调入(换出时已写到了对换区)
- 共享页若已被其他进程调入内存:不必再调,直接映射即可
工程现状:第 ③ 种就是现代 Linux 的做法,而且它把这个思想推到了更彻底的形态——按页的来源分成两类:
- 文件映射页(file-backed):内容来自某个文件(可执行代码、
mmap的文件、页缓存)。回收时若是干净页直接丢弃,脏页写回原文件,完全不占用 swap。 - 匿名页(anonymous):没有对应文件(堆、栈、匿名
mmap)。它们无处可写,只能换出到 swap。
对照一下就会发现,这正是教材第 ② 档”按会不会被修改分流”的工程版本:有文件作后盾的页不需要 swap,没有后盾的才需要。
关联对照:Linux 的 swap 与 macOS 的内存管理
fallocate与 swap 不是一回事。fallocate只是预分配一个指定大小的文件(占住磁盘块),它本身没有任何”交换”语义。要让这个文件成为交换空间,还需要mkswap格式化、swapon启用。fallocate造的是容器,swap 是用途。“服务器上设置了 swap 却从来没用过”是正常的,而且是好事。 swap 只在内存压力大到需要换出匿名页时才会被使用。使用率长期为 0 说明内存充裕,没有触发换出。Linux 的
swappiness参数正是用来调节”多大压力下开始换出匿名页”的倾向。macOS 很少用 swap,是因为它在 swap 之前多了一层:内存压缩。不活跃的页先被就地压缩留在内存中,只有压缩也扛不住时才真正写到磁盘。这比磁盘 I/O 快得多。
“已缓存文件特别高”则完全是另一回事——那是页缓存(file-backed pages),属于内存映射文件的范畴。这部分内存随时可回收(干净页直接丢弃、脏页写回),因此”已用内存高”并不代表内存紧张。这也是为什么 Linux 有句名言:空闲的内存是被浪费的内存——与其空着,不如拿来缓存文件。
边界
缺页中断 vs 一般中断
| 缺页中断 | 一般(外)中断 | |
|---|---|---|
| 来源 | 当前指令执行期间,内部产生 | 外部设备发出 |
| 类别 | 内中断 · 故障(fault) | 外中断 |
| 检测时机 | 指令执行过程中 | 一条指令执行完之后 |
| 处理后 | 重新执行被中断的指令 | 执行下一条指令 |
| 一条指令可产生几次 | 可能多次 | 通常一次 |
“处理后重新执行原指令”是缺页中断最独特的一点,因为那条指令压根没成功。
请求分页 vs 基本分页
判据:页表项是否含存在位、是否有缺页中断机构。
基本分页要求所有页都必须在内存,页表项里没有”不在内存”这种状态;请求分页允许部分页在外存,因此必须有存在位、缺页中断和置换算法。
修改位的价值
修改位不影响”淘汰谁”的正确性,只影响”淘汰的代价”。
即使完全不看修改位,淘汰后无脑写回也不会出错,只是每次都多一次磁盘写。修改位的作用是把这次写在多数情况下省掉——因为大部分被访问的页(尤其是代码页)根本没被改过。
这也解释了改进型 Clock 算法为什么要同时看 A 位和 M 位。
对照速查
| 请求页表新增字段 | 作用 |
|---|---|
| 存在位 P | 该页是否在内存——缺页判断的唯一依据 |
| 访问位 A | 近期是否被访问——供置换算法参考 |
| 修改位 M | 是否被修改——决定淘汰时要不要写回 |
| 外存地址 | 缺页时去哪儿取 |
| 调页策略 | 何时调入 | 代价 |
|---|---|---|
| 请求调页 | 缺页时才调 | 缺页中断频繁 |
| 预调页 | 提前批量调入 | 预测失败则浪费;主要用于首次调入 |
| 调入来源 | 判据 |
|---|---|
| ① 对换区充足 | 全部从对换区(运行前先从文件区拷过去) |
| ② 对换区不足 | 只读的走文件区(不必写回),可改的进对换区 |
| ③ UNIX 方式 | 未运行过走文件区,换出过走对换区;共享页已在内存则不调 |
考点
- 请求页表的四个新增字段及各自用途
- 缺页中断是内中断中的”故障”,处理后重新执行原指令
- 一条指令可能引发多次缺页中断
- 只有 M = 1 才需写回外存
- 预调页主要用于进程首次调入
- 三种调入来源,判据分别是”对换区够不够”和”会不会被修改 / 有没有运行过”
链接
- 🏠 返回总览:操作系统第 3 章:内存管理总览
- ⬅️ 上一节:3.2.1 虚拟内存的基本概念
- ➡️ 下一节:3.2.3 页框分配
- 📖 名词库:第 3 章名词库