段页式存储管理

前两节各解决了一半问题,又各留下一半:

  • 分页消灭了外部碎片,但不便于共享和保护
  • 分段便于共享和保护,但外部碎片又回来了

段页式的做法是把两者叠起来:用分段满足用户的逻辑需求,用分页解决物理分配的碎片问题。

机制

两层划分

疑问点:段可变长与按固定页框分配是否矛盾

教材原文:“先按逻辑分段,再把每个段分页。逻辑上段还是可变长;但实际分配物理内存时,是按固定大小的页框分配。”

这句话难以理解——既然段是可变长的,为何又说按固定大小分配?

关键在于:这句话里的”可变长”和”固定大小”说的不是同一样东西。

  • “段可变长” 说的是——每个段有多大,这是逻辑层面的事
  • “按固定页框分配” 说的是——装它的容器有多大,这是物理层面的事

两者能同时成立,是因为段的可变长最终体现为”占用的页数不同”,而不是”页的大小不同”。

用一个具体例子把它落实(设页面大小 4KB):

段段长(可变)需要几页实际占用内部碎片
主程序段10 KB 页12 KB2 KB
数据段4 KB 页4 KB0
栈段5 KB 页8 KB3 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 存的是段表项,命中后只得到页表始址”——不是这里的快表模型;它缓存的是已经完成两级查表的地址变换结果,能直接给出页框号。
  • “TLB 存的是页面中的指令或数据”——错误;TLB 存地址映射,数据 Cache 才缓存指令或数据内容。

越界检查

段页式仍需两次检查,但第二次的对象变了:

  1. 段号 S ≥ 段表长度? → 越界中断
  2. 页号 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)223
便于共享保护否是是
越界检查分段段页式
第一次段号 vs 段表长度段号 vs 段表长度
第二次段内偏移 vs 段长页号 vs 该段页表长度

考点

  • “段可变长”体现为页数不同,不是页的大小不同
  • 地址结构三段式,页号+页内偏移合起来才是段内地址
  • 一张段表 + 每段一张页表(数量关系)
  • 段表项存的是页表始址,不是段的基址
  • TLB 存页面到页框的映射:以段号 + 段内页号匹配,得到物理页框号;页内偏移保持不变
  • 访存 3 次(段表、页表、数据),TLB 几乎必需
  • 地址仍是二维,不是三维
  • 碎片:有内部、无外部,且内部碎片多于纯分页

链接