什么是分段和分页?
两者都是把虚拟地址翻译成物理地址的方案:分段按程序的逻辑结构(代码段、数据段、栈段)切成变长的块, 分页把地址空间切成固定大小的页(常见 4KB)。分段贴近程序员视角但会产生外部碎片, 分页消除外部碎片但要靠多级页表 + TLB 才能查得快。现代 x86-64 实际上以分页为主,分段只留下最小化的段基址。
1. 两种映射
分段(segment):
逻辑地址 = [段号 s][段内偏移 d]
段表[s] = {基址 base, 限长 limit}
物理地址 = base + d (d 必须 < limit,否则段错误)
分页(paging):
虚拟地址 = [页号 p][页内偏移 d]
页表[p] → 物理页框号 f
物理地址 = f × 页大小 + d
x86-64 的实际路径(段页式):
段寄存器(基址≈0) ─► 线性地址 ─► PML4 → PDPT → PD → PT(4 级)─► 物理地址
每级 9 位索引 + 页内 12 位偏移 = 48 位
虚拟地址 48 位:
┌────────┬────────┬────────┬────────┬────────────┐
│ PML4 9 │ PDPT 9 │ PD 9 │ PT 9 │ offset 12 │
└────────┴────────┴────────┴────────┴────────────┘
每级页表 512 项 × 8 字节 = 4KB,正好一页
2. 对比
| 维度 | 分段 | 分页 |
|---|---|---|
| 划分依据 | 逻辑意义(代码/数据/栈) | 物理固定大小 |
| 块大小 | 变长 | 固定(4KB,可 2MB/1GB 大页) |
| 碎片 | 外部碎片(空洞难利用) | 内部碎片(最后一页浪费) |
| 对程序员可见 | 可见(段错误、段权限) | 透明 |
| 表项内容 | 基址 + 限长 + 保护位 | 页框号 + 权限 + 脏位/访问位 |
| 共享与保护 | 按段共享 | 按页共享(COW、mmap) |
| 换入换出 | 整段,粒度粗 | 按页,粒度细 |
分页解决了"外部碎片",代价是"内部碎片":一个 1 字节的文件也占一页 4KB; 同时页表本身要占内存,32 位单级页表要 4MB,64 位单级页表则完全不可行,所以必须多级。
3. 多级页表省在哪
32 位、4KB 页、单级页表:
2^20 个页表项 × 4B = 4MB,每个进程都要一份
64 位、4 级页表:
只给"实际用到的地址区间"分配下级页表
一个只用了 3 个页的进程,只需 1 个 PML4 + 1 个 PDPT + 1 个 PD + 1 个 PT ≈ 16KB
代价是一次地址翻译要访存 4 次(每级一次),于是有了 TLB(Translation Lookaside Buffer): 把最近的页表项缓存在 CPU 里,命中就只需 1 个周期;未命中才走"页表遍历"(page walk),慢几十倍。 这也是为什么上下文切换、大内存随机访问会变慢——TLB 被刷或被挤掉。
4. 观测与调优
getconf PAGE_SIZE # 4096
cat /proc/meminfo | grep -i huge # 大页池与使用情况
# 看进程的虚拟地址段(哪些是映射文件、哪些是匿名页)
cat /proc/$$/maps | head
# 打开透明大页(THP),减少 TLB miss
echo always > /sys/kernel/mm/transparent_hugepage/enabled
# 预留 2MB 大页给数据库
echo 1024 > /proc/sys/vm/nr_hugepages
// 显式申请大页,减少 TLB 压力(2MB 一页,TLB 覆盖范围放大 512 倍)
void *p = mmap(NULL, 2 * 1024 * 1024, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
5. 常见追问
Q:为什么页大小是 4KB? 历史折中:页太小页表项爆炸、IO 次数多;页太大内部碎片严重、内存浪费。 4KB 与磁盘块、页表项大小(512×8B)刚好对齐,硬件实现也简单。
Q:缺页中断(page fault)流程?
CPU 访问的页不在内存 → 触发缺页异常 → 内核检查地址合法性 → 分配页框 → 从磁盘/交换区读入 →
更新页表 → 返回用户态重新执行那条指令。非法地址则发 SIGSEGV。
Q:fork 之后为什么很快? 因为用了写时复制(Copy-On-Write):父子共享同一批物理页并全部标只读, 任何一方写入才触发缺页,此时才真正复制那一页。这也是"分页按页共享"的直接收益。
Q:段错误和缺页是一回事吗? 不是。缺页是"页不在内存",可能合法(按需加载)也可能非法;段错误(SIGSEGV)是访问了无权限或未映射的地址, 属于非法缺页的最终结果。
小结
分段是"按逻辑切、变长、有外部碎片",分页是"按固定大小切、透明、有内部碎片"。 现代系统用多级页表 + TLB + 大页把分页的开销压下去,分段退化为权限检查的辅助手段。 答题顺序:两种映射 → 碎片与页表空间 → TLB 与缺页 → COW 与大页。