什么是分段和分页?


一则或许对你有用的小广告

欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书

  • 《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于 Spring AI + Spring Boot 3.x + JDK 21...查看介绍

  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...查看介绍;演示链接:http://116.62.199.48:7070/

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/

  • 新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍

截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. 基础掌握度:面试官不仅仅是想听你背出两个定义,更是想知道你是否理解它们各自解决什么问题——为什么连续内存分配撑不住,非得搞分页或分段。

  2. 原理深度:地址翻译的细节是重灾区。页表怎么查、段表怎么查、多级页表为什么存在、TLB 起什么作用,一层层追问下来,背题的人很快就露馅。

  3. 对比与落地能力:能否说清两者的优劣(内部碎片 vs 外部碎片)、以及真实系统怎么选(比如 x86-64 上的 Linux 其实基本放弃了分段),这一层能答上来,直接和普通人拉开差距。

核心答案

先说结论,一句话版本:

  • 分页(Paging):把虚拟地址空间和物理内存都切成固定大小的块(页 / 页框),通过页表建立映射。核心目的是提高内存利用率,消除外部碎片。
  • 分段(Segmentation):按程序的逻辑结构划分成大小不等的段(代码段、数据段、栈段……),通过段表建立映射。核心目的是满足程序模块化和保护的需求

两者最本质的区别:分页对程序员透明、是物理层面的机械划分;分段对程序员可见、是逻辑层面的按需划分

一张表先拉通全局:

对比维度 分页 分段
划分单位 固定大小(典型 4KB) 大小不等,按逻辑模块
划分依据 物理划分,对程序员透明 逻辑划分,程序员可见
地址组成 页号 + 页内偏移 段号 + 段内偏移
映射工具 页表(页号 → 页框号) 段表(段基址 + 段界限)
碎片问题 有内部碎片,无外部碎片 有外部碎片,无内部碎片
共享与保护 粒度是页,稍显粗糙 粒度是逻辑段,天然适合
主要目的 内存利用率 模块化、共享、保护

连续分配外部碎片
连续分配外部碎片

深度解析

一、为什么需要它们:连续分配的困境

要理解分页分段,得先知道它们是来救场的。

早期操作系统把进程整块塞进连续的物理内存,问题很快暴露:内存被反复分配回收后,会留下一个个不连续的小空洞。这些空洞加起来可能有好几百 MB,但每一个都塞不下新进程——这就是臭名昭著的外部碎片(External Fragmentation)

当年这块坑了不少人,紧凑(compaction,把进程挪到一起合并空洞)虽然能救,但搬移代价太大。于是业界给出了两条路:分页和分段。

二、分页:固定大小,机械切分

分页的思路很直接:既然整块分配难,那就切成小块。

  • 虚拟地址空间按固定大小切成页(Page),x86/x86-64 上典型是 4KB
  • 物理内存按同样大小切成页框(Page Frame)
  • 任何一页可以放到任何一个页框里,操作系统通过**页表(Page Table)**记录这层映射关系

地址翻译流程看图:

分页地址转换
分页地址转换

上图展示了分页的地址翻译全过程,拆开讲:

  • 拆地址:虚拟地址被硬件拆成两部分——高位是页号,低位是页内偏移。4KB 的页对应 12 位偏移(2^12 = 4096)
  • 查页表:MMU(内存管理单元)拿页号去页表里查,找到对应的页框号
  • 拼物理地址:页框号换掉虚拟地址里的页号,偏移原样拼上,就是物理地址
  • 缺页处理:如果页表项标记该页不在内存(比如还没加载或被换出到磁盘),硬件触发缺页中断,操作系统负责把它调进来

这里有两个高频追问点,先埋一下:

分页页表映射
分页页表映射

1. 为什么需要多级页表?

32 位系统上 4KB 分页,页表项 4 字节,一个进程的页表理论上有 2^20 个表项,就是 4MB——每个进程都要一张,太夸张了。而且大部分虚拟地址空间根本没用到,却也得占表项。

多级页表把页表本身也分页,上层表项指向下层页表页,没用的区域对应的下层页表干脆不分配。x86-64 上通常是 4 级页表(PGD → PUD → PMD → PTE),代价是每次翻译最多要查 4 次内存。

2. 查 4 次内存太慢怎么办?——TLB

MMU 里有个专门的高速缓存叫 TLB(Translation Lookaside Buffer,翻译后备缓冲区),缓存最近的 “虚拟页号 → 物理页框号” 映射。命中直接出结果,速度接近 CPU 缓存;不命中才走那 4 次内存查询。程序的局部性原理让 TLB 命中率通常能到 99% 以上,这才是分页能商用的底气。

三、分段:按逻辑切,大小随意

分段走的是另一条路:既然程序天然就有逻辑结构,那就按结构切。

一个程序会被分成代码段、数据段、栈段等,每段有自己的名字、长度和用途,段内地址连续,段与段之间独立。

分段地址转换
分段地址转换

分段这边翻译流程更短,过一遍:

  • 拆地址:虚拟地址 = 段号 + 段内偏移,这个二元组就是早期程序员熟悉的 “段 x: 偏移 y” 地址形式
  • 查段表:段表项里有两个关键字段——段基址(base)段界限(limit)
  • 越界检查:偏移必须小于段界限,否则就是越界访问,硬件直接抛异常。你写 C 时天天见的 Segmentation Fault(段错误),名字就是这么来的
  • 算物理地址:段基址 + 偏移,直接线性相加,比分页还简单

分段的优雅之处在于它符合程序的世界观:代码是只读可共享的,数据是可写的,栈是动态增长的——每个段可以单独设置权限。共享库只要把代码段映射进不同进程就行。但它的死穴也在这里:段长度不固定,分配后照样产生外部碎片,绕了一圈好像又回到了老问题。

分段段表转换
分段段表转换

四、两者怎么选:现实世界的答案

历史上先后出现过三种玩法:

  1. 纯分页:简单粗暴,现代主流。Linux 在 x86-64 上就是这么干的
  2. 纯分段:早期 x86(16 位实模式)的玩法,段寄存器 CS/DS/SS 就是那个时代的遗产
  3. 段页式:先分段、段内再分页,地址翻译先查段表再查页表。x86 保护模式(32 位)硬件上支持这套,兼顾逻辑性和利用率

真正有意思的是后来的演进:x86-64 长模式下,Linux 干脆把 CS/DS/ES/SS 的基址都设成 0、界限拉满,搞了个 “平坦内存模型(Flat Model)”,分段机制名存实亡,只剩这些段寄存器还承担权限等级(Ring 0 / Ring 3)的标记作用。不过有个例外得知道:FSGS 这两个段寄存器在 64 位模式下仍保留了可用的基址,Linux 拿它们实现 per-CPU 数据和线程本地存储(TLS),算是分段留下的一点火种。也就是说,现代 Linux 的内存管理实际上就是分页独挑大梁(长模式下分页甚至是强制的),逻辑保护靠页表项里的读写执行位来实现。

至于 Java 同学关心的延伸:JVM 运行时数据区里的堆、栈、方法区,那是对内存的逻辑划分,跟 CPU 层面的分段不是一回事,别在面试里混为一谈。不过 G1、ZGC 这些收集器管理大堆时用到的大页(Huge Page,2MB / 1GB 页),倒是实打实的分页技术落地——页更大,TLB 能覆盖更多内存,翻译开销更小。

Linux 分页模型
Linux 分页模型

面试高频追问

  1. 追问一:什么是内部碎片和外部碎片?分页和分段各有哪些?

    • 内部碎片:分配单位内部用不满的部分。分页里进程最后一页平均浪费一半;分段没有
    • 外部碎片:内存中无法利用的小空洞。分段有(需要紧凑或换出);分页没有(任何页框都能装任何页)
  2. 追问二:为什么多级页表能省内存,单级不行吗?

    • 单级页表必须为整个虚拟地址空间预留连续的表项空间;多级页表利用 “大部分地址空间未被映射” 的事实,下层页表按需分配,稀疏地址空间下省得不是一点半点。代价是翻译次数变多,靠 TLB 撑住。
  3. 追问三:TLB 是什么?为什么切换进程要刷 TLB?

    • TLB 缓存虚拟页号到物理页框号的映射。进程切换后页表也换了,旧映射全部失效,所以要刷新。现代处理器给页表基地址寄存器(如 x86 的 CR3)配了进程 ID 标签(ASID/PCID),可以让多个进程的映射共存,减少无谓刷新。
  4. 追问四:缺页中断和普通中断有什么区别?

    • 缺页中断是在一条指令执行期间产生并处理的(指令还没执行完就要去补页),而且一条指令可能触发多次缺页;普通中断一般在指令周期结束后响应。

常见面试变体

  • 变体一:什么是虚拟内存?它和物理内存怎么映射?
  • 变体二:说说什么是缺页中断,页面置换算法有哪些(LRU、Clock 等)?
  • 变体三:为什么 x86-64 上 Linux 基本放弃了分段?
  • 变体四:什么是大页 / 透明大页(THP)?对性能有什么影响?

记忆口诀

  • 分页:定长切块,物理视角,求利用率——“页等框,碎片藏页内”
  • 分段:变长切块,逻辑视角,求保护共享——“段随心,碎片散页外”
  • 一句话区分:页是物理的、盲切的;段是逻辑的、按需的

总结

分页和分段是操作系统摆脱连续分配困境的两条路:分页用固定大小的页换取内存利用率和实现简单,代价是内部碎片和多级页表的开销;分段用逻辑单位换共享与保护,代价是外部碎片。现代 x86-64 Linux 已经用平坦模型把分段边缘化,分页 + TLB + 多级页表才是当下内存管理的主角。把地址翻译那条链路讲顺,这道题基本就稳了。