段页式存储管理
前两节各解决了一半问题,又各留下一半:
段页式的做法是把两者叠起来:用分段满足用户的逻辑需求,用分页解决物理分配的碎片问题。
机制
两层划分
疑问点:段可变长与按固定页框分配是否矛盾
教材原文:“先按逻辑分段,再把每个段分页。逻辑上段还是可变长;但实际分配物理内存时,是按固定大小的页框分配。”
这句话难以理解——既然段是可变长的,为何又说按固定大小分配?
关键在于:这句话里的”可变长”和”固定大小”说的不是同一样东西。
- “段可变长” 说的是——每个段有多大,这是逻辑层面的事
- “按固定页框分配” 说的是——装它的容器有多大,这是物理层面的事
两者能同时成立,是因为段的可变长最终体现为”占用的页数不同”,而不是”页的大小不同”。
用一个具体例子把它落实(设页面大小 4KB):
| 段 | 段长(可变) | 需要几页 | 实际占用 | 内部碎片 |
|---|---|---|---|---|
| 主程序段 | 10 KB | 12 KB | 2 KB | |
| 数据段 | 4 KB | 4 KB | 0 | |
| 栈段 | 5 KB | 8 KB | 3 KB |
三个段长度各不相同(10K、4K、5K),这就是可变长;但装它们的每一页都是 4KB,这就是固定大小。段的差异体现在页数上(3 页、1 页、2 页),不体现在页的尺寸上。
一个便于记忆的类比
把段想象成一本书的章节,把页想象成固定行数的稿纸。
章节的长短是内容决定的,你不能要求每一章一样长——这就是”段可变长”。 但抄写用的稿纸每张行数相同——这就是”页固定大小”。
长章节用多张稿纸,短章节用少张稿纸;最后一张往往写不满,那点空白就是内部碎片。
而稿纸抄完之后可以散放在书架的任意位置,不必挨着——这就是”离散分配”,外部碎片由此消失。
为什么这样叠加是有效的:
分段那一层保留了逻辑完整性,共享和保护仍可以按段进行;分页那一层负责物理落地,每个段被切成整页去占用任意空闲页框,于是外部碎片被彻底消除。
代价是引入了内部碎片——每个段的最后一页通常装不满。注意这里的碎片量比纯分页更多:纯分页是整个进程浪费半页,段页式是每个段各浪费半页。
地址结构
逻辑地址被拆成三段:
注意后两段的关系:页号和页内偏移合起来,才是原来分段里的”段内偏移”。也就是说段页式是把分段的段内地址又按分页的规则拆了一次。
地址变换:三次访存
flowchart TB LA["逻辑地址 <b>段号 S</b> | <b>页号 P</b> | 页内偏移 W"]:::la R["段表寄存器<br/>(段表始址 + 段表长度)"]:::reg S1["<b>① 查段表</b><br/>得该段的页表始址"]:::step S2["<b>② 查页表</b><br/>得页框号 F"]:::step S3["<b>③ 访问目标单元</b><br/>物理地址 = F ‖ W"]:::pa LA --> R --> S1 --> S2 --> S3 classDef la fill:#dbeafe,stroke:#2563eb,color:#1e3a5f classDef reg fill:#f1f5f9,stroke:#94a3b8,color:#334155 classDef step fill:#fef3c7,stroke:#d97706,color:#78350f classDef pa fill:#dcfce7,stroke:#16a34a,color:#14532d
无快表或快表未命中时,一次访问通常需要三次访存:查段表、查页表、取数据(假设所需表项和页面均已在内存,不发生缺页)。这是段页式最大的性能代价,因此用 TLB 加速地址变换——TLB 命中时可直接由”段号+页号”得到页框号,一次主存访问即可取得数据。
表的数量关系要分清:
- 每个进程一张段表
- 每个段一张页表
所以一个进程有 1 张段表、
段表项与纯分段不同——它存的不再是”段的基址”,而是该段页表的始址和长度:
| 段号 | 页表长度 | 页表始址 |
|---|
TLB 存什么:按 408 常用教材模型回答
可以直接用于答题的表述
TLB(快表)存放最近访问的部分页面的地址变换信息,也可称为部分页表项的缓存。在段页式中,以“段号 + 段内页号”标识一个页面,记录它对应的物理页框号。
核心对应关系是
| 段号 | 段内页号 | 物理页框号 |
|---|---|---|
| 标识哪个段 | 标识该段的哪一页 | 给出这一页在主存中的位置 |
实际表项还可包含有效位、访问权限等控制信息;回答“存什么”时,抓住段号、页号、页框号这三个核心字段即可。
🔗 字段对照见 CO 3.6.2:页表项字段表;TLB 如何在此基础上增加标记,见 TLB 项的组成。CO 该节讲的是页式模型,本节段页式的页面标识则是段号 + 段内页号。
为什么要带段号? 各段都有自己的页表,两个不同段都可以有“第 0 页”,但对应的页框可能不同。因此在这个段页式模型中,不能只拿段内页号去匹配,必须连同段号一起识别页面。
命中以后怎么用? 用
三个容易混淆的说法:
- “TLB 缓存部分页表项”——可以;在本节中最好补全为带段号、段内页号标记的页面到页框的映射。
- “TLB 存的是段表项,命中后只得到页表始址”——不是这里的快表模型;它缓存的是已经完成两级查表的地址变换结果,能直接给出页框号。
- “TLB 存的是页面中的指令或数据”——错误;TLB 存地址映射,数据 Cache 才缓存指令或数据内容。
越界检查
段页式仍需两次检查,但第二次的对象变了:
- 段号 S ≥ 段表长度? → 越界中断
- 页号 P ≥ 该段的页表长度? → 越界中断
页内偏移 W 不需要检查——它的位数固定,天然不会超出页面大小。这一点与纯分段不同(纯分段要检查段内偏移是否超过段长)。
边界
三种方式的地址维度
段页式的逻辑地址仍然是二维的,不是三维。
原因是:页号和页内偏移是系统自动从”段内地址”里截出来的,对用户不可见。 程序员编程时给出的仍然只有段号和段内地址两个信息,与纯分段完全一样。
判据还是那一条:用户需要显式给出几个数? 段页式需要给两个(段号、段内地址),所以是二维。
段页式的碎片:内部有,外部无
| 内部碎片 | 外部碎片 | |
|---|---|---|
| 分页 | 有(进程最后一页) | 无 |
| 分段 | 无 | 有 |
| 段页式 | 有(每段最后一页) | 无 |
段页式继承了分页的碎片形态,因为物理分配是按页框进行的。而且如上所述,它的内部碎片总量通常大于纯分页——段数越多,浪费越多。
与 JVM 内存布局的关系
疑问点:段页式与 JVM 的内存划分是否同源
JVM 的内存区域划分与段页式管理颇为相似。JVM 出现的时间是否足够早? 早期是否也经历了从连续分配演变而来的过程?
相似是真的,但两者没有历史上的传承关系,而且不在同一个层次上。
时间线先厘清:段页式管理出现在 1960 年代(Multics 系统是代表),而 JVM 发布于 1995 年。段页式远早于 JVM,不存在”JVM 影响了操作系统”的可能。
层次差异才是关键:
| 操作系统的段页式 | JVM 的内存区域划分 | |
|---|---|---|
| 由谁实现 | 内核 + 硬件(MMU) | 用户态的运行时 |
| 管理的对象 | 进程的虚拟地址空间 → 物理页框 | JVM 自己从 OS 申请来的那一块虚拟地址空间 |
| 是否涉及页表 | 是,由硬件查表 | 否,纯软件记账 |
也就是说:JVM 的”分区”是建立在操作系统已经提供的虚拟地址空间之上的。 JVM 把方法区、堆、栈划分好之后,这些区域最终仍然要由操作系统按页映射到物理页框。
两层是叠加关系,不是同一层的两种做法。
那为什么看起来像?因为它们采用了同一种通用思想:按逻辑功能划分区域,便于分别管理和保护。这个思想在计算机系统中被反复使用,不限于这两处。
倒是有一处对应更贴切:JVM 堆内部的划分——尤其是 G1 收集器把堆切成大小相同的 Region——在形态上更接近”分页”而非”分段”。理由完全一致:固定大小的块便于分配、回收和整理,还能避免碎片。 这与分页消除外部碎片的动机是同一个。
范围提示:这部分内容不在 408 考纲内,此处列出仅为把两个层次分开,避免用 JVM 的印象去套操作系统的机制。
段页式解决了什么、没解决什么
解决了:外部碎片(靠分页)、共享与保护的粒度(靠分段)。
没解决:内部碎片仍在,而且比纯分页更多;访存次数增加到 3 次,性能下降(靠 TLB 缓解)。
这说明段页式不是”更优”的方案,而是”更贵但更全面”的方案。 实际系统的选择要看权衡——现代主流系统(Linux、Windows)实际上以分页为主,段的功能被大幅弱化。
对照速查
| 分页 | 分段 | 段页式 | |
|---|---|---|---|
| 地址结构 | 页号 ‖ 偏移 | 段号 ‖ 段内偏移 | 段号 ‖ 页号 ‖ 偏移 |
| 地址维度 | 一维 | 二维 | 二维 |
| 长度 | 固定 | 可变 | 段可变、页固定 |
| 内部碎片 | 有 | 无 | 有(每段末页) |
| 外部碎片 | 无 | 有 | 无 |
| 表的数量 | 每进程 1 张页表 | 每进程 1 张段表 | 1 张段表 + 每段 1 张页表 |
| 访存次数(无 TLB) | 2 | 2 | 3 |
| 便于共享保护 | 否 | 是 | 是 |
| 越界检查 | 分段 | 段页式 |
|---|---|---|
| 第一次 | 段号 vs 段表长度 | 段号 vs 段表长度 |
| 第二次 | 段内偏移 vs 段长 | 页号 vs 该段页表长度 |
考点
- “段可变长”体现为页数不同,不是页的大小不同
- 地址结构三段式,页号+页内偏移合起来才是段内地址
- 一张段表 + 每段一张页表(数量关系)
- 段表项存的是页表始址,不是段的基址
- TLB 存页面到页框的映射:以段号 + 段内页号匹配,得到物理页框号;页内偏移保持不变
- 访存 3 次(段表、页表、数据),TLB 几乎必需
- 地址仍是二维,不是三维
- 碎片:有内部、无外部,且内部碎片多于纯分页
链接
- 🏠 返回总览:操作系统第 3 章:内存管理总览
- ⬅️ 上一节:3.1.4 基本分段存储管理
- ➡️ 下一节:3.2.1 虚拟内存的基本概念
- 📖 名词库:第 3 章名词库