高 进阶
分页与分段#
一句话答案#
分页把内存切成固定大小的页(如 4KB),消除外部碎片、便于换入换出,但对程序员不可见;分段按程序逻辑(代码段/数据段/栈)切成变长段,有语义、便于共享和保护,但有外部碎片;现代系统用段页式:先分段再对每段分页,兼得两者优点。
核心要点
| 维度 | 分页 Paging | 分段 Segmentation |
|---|---|---|
| 划分依据 | 固定大小(物理决定) | 逻辑单位(程序决定) |
| 大小 | 等长(如 4KB) | 变长 |
| 碎片 | 仅内部碎片(最后一页用不满) | 外部碎片(段间空隙) |
| 地址 | 一维(页号+页内偏移自动拆) | 二维(段号+段内偏移) |
| 对用户 | 透明 | 可见,有语义 |
| 优点 | 利用率高、便于换页 | 便于共享/保护/动态增长 |
段页式: 虚拟地址 = 段号 + 页号 + 页内偏移,先查段表得到该段页表基址,再查页表得到物理页帧。x86 即段页式,但 Linux 把段基址都设为 0、弱化分段,实质以分页为主。
面试回答(2分钟版)
分页和分段是两种把虚拟地址映射到物理内存的方式,区别在于切分的依据。分页是把地址空间切成固定大小的页,物理内存切成同样大小的页帧,通过页表做映射,页大小通常 4KB。它的好处是没有外部碎片,因为每页等长可以随便填空位,只有最后一页可能用不满产生少量内部碎片;而且固定大小特别便于换入换出磁盘。缺点是分页纯粹按物理大小切,对程序员是透明的,没有逻辑含义。分段则是按程序的逻辑结构来切,比如代码段、数据段、堆、栈各成一段,每段长度可变。好处是有语义,便于做共享和保护,比如多个进程共享同一份代码段、给代码段设只读权限,而且段可以动态增长比如栈。缺点是段长度不固定,分配回收久了会产生外部碎片,需要内存紧缩。地址形式上也不同,分页是一维地址,硬件自动按页大小拆成页号和页内偏移;分段是二维地址,需要显式的段号加段内偏移。实际系统两者结合用段页式:先按逻辑分段,再对每个段内部分页,先查段表找到段的页表,再查页表找到物理帧,这样既保留了分段的逻辑语义和共享保护,又用分页解决了外部碎片和换页问题。x86 架构就是段页式,不过 Linux 为了可移植性把所有段基址设成 0、几乎不用分段,实际上是以分页为主。
追问与易错
追问方向:
- “内部碎片和外部碎片区别?”→ 内部碎片是分配单位内部没用满(分页最后一页),外部碎片是空闲内存总量够但不连续无法分配(分段)。分页只有内部碎片,分段主要是外部碎片
- “为什么页大小一般是 4KB?不能更大吗?”→ 太小则页表项过多、页表占内存大;太大则内部碎片严重。4KB 是经验折中。也支持大页 HugePages(2MB/1GB)减少页表项、提升 TLB 命中率
- “段页式下访问一次内存要查几次表?”→ 逻辑上要查段表 + 页表 + 真正访存共 3 次,所以靠 TLB 缓存最近的映射,命中后直接出物理地址
- “为什么 Linux 弱化分段?”→ 分段不利于跨架构移植(很多 RISC 架构无分段硬件),Linux 把段基址统一设 0 做”平坦模型”,靠分页 + mmap 实现内存区域的语义和保护
易错点:
- ❌ 分页有外部碎片——分页只有内部碎片,外部碎片是分段的问题
- ❌ 分段比分页先进所以更常用——现代主流是分页/段页式,纯分段早已淘汰
- ❌ 页大小越大越好——大页减少页表项但加剧内部碎片,需权衡