48 位地址、4 KB 页、每张表 512 项,正好拆四级,每级正好占一页。省下的全是【地址空间里的空洞】——为 4 GB 的进程建表,512 GB 变 8 MB。
5.5 算出单级页表要 512 GB,这一节看它怎么被拆成四级、为什么每级正好一页、代价是什么。
一、为什么正好四级
页号有 36 位。 如果每张表的项数定为 512 项(2 的 9 次方),那每一级就吃 9 位页号。 36 ÷ 9 = 4——四级正好用完。 512 项 × 8 字节 = 4096 字节 = 4 KB——每张表正好占一页。 这不是巧合:表的大小设计成页大小,才能像普通页一样分配、换出、缓存。所以【512 项】这个数是被【4 KB 页】和【8 字节项】定死的。
二、四级怎么查
把 36 位页号切成四段,每段 9 位。 第一段查一级表,拿到二级表的地址;第二段查二级表,拿到三级表的地址……四级走完,才拿到物理页号。 每一级没用到的那 511 项,对应的整棵子树都不用建。这就是省下 512 GB 的全部秘密。 为 4 GB 的进程建四级页表,大约只要 8 MB——因为只有真正用到的地址段才长出了表。 地址空间里的空洞本来就没有页,现在连表也不用建了。
三、代价:多查几次
单级查 1 次,四级查 4 次。 如果每次都真去内存查,一次访存要变五次——慢到无法接受。 所以必须有 TLB:CPU 里缓存【页号 → 物理页号】的翻译结果,命中就一次都不用查。 实测 TLB 命中率通常 95% 以上(因为有局部性),所以四级页表的实际代价很小。 换句话说:多级页表能成立,全靠 TLB 兜底;没有 TLB,这个设计根本不可用。
四、为什么后来又有五级
因为地址位宽涨了:从 48 位扩到 57 位,页号从 36 位变 45 位。 45 ÷ 9 = 5——所以变成五级。 五级能管 128 PB 的虚拟地址空间。 代价:最坏情况多一次访存;TLB 未命中时更慢。 所以级数是【被地址位宽逼出来的】,不是越多越好。
五、动手 Demo:多级页表——把一张大表拆成四张小表
这一节把【几级、每级多大、省多少】全部算一遍,并对比同一个 4 GB 进程在单级与四级下的页表开销。
vbits, per = 48, 512
levels = (vbits - 12) // 9
print("页号位数", vbits - 12)
print("每级管几位", 9)
print("需要几级", levels)
print("每张表大小", per * 8, "字节")
one = (1 << (vbits - 12)) * 8
print("单级页表", one / 1024 ** 3, "GB")
实跑输出:
--------------------------------------------------------------------
多级页表——把一张大表拆成四张小表
--------------------------------------------------------------------
① 48 位地址、4KB 页、每张表 512 项,正好拆几级
算的是 算式 结果
--------------------------------------------------------
页号有多少位 48 − 12 36 位
每级管几位 512 = 2 的 9 次方 9 位
需要几级 36 ÷ 9 4 级
每张表多大 512 × 8 字节 4 KB(正好一页)
**每张表正好占一页**——这不是巧合,是设计成这样才好分配。
② 单级 vs 四级,差在【没用的部分】
方案 要给 4 GB 进程建多少 占用
------------------------------------------------
单级页表 整个虚拟空间都要建表 512.00 GB
四级页表 只为用到的那部分建 8 MB
同一个进程,**512.00 GB 对 8 MB**,差 65408 倍。
**省下来的全是【地址空间里的空洞】——那些空洞本来就没有页,不需要表。**
③ 代价:多查几次
方案 查表次数 怎么补
----------------------------------------------
单级 1 次 表太大,建不起
四级 4 次 **靠 TLB 挡住绝大多数**
**这就是 6.7 要讲的 TLB**:**它是页表的缓存,命中就不用一级级查了。**
**没有 TLB,多级页表根本没法用。**
④ 换成 5 级呢
位数 级数 能管的虚拟空间
----------------------------------
48 位 4 级 256 TB
57 位 5 级 128 PB
**级数是被【地址位宽】逼出来的,不是越多越好。**
**每多一级,最坏情况就多一次访存。**
36 位页号 ÷ 9 位每级 = 4 级,每张表 4096 字节,正好一页。 单级要 512 GB,四级为 4 GB 的进程只要 8 MB 左右——差六万多倍。 注意省下来的全是【没用到的地址段】:进程的地址空间大部分是空的,空的就不长表。 这就是为什么【虚拟地址空间可以比物理内存大得多】而不浪费内存:没用到的部分,连记账的钱都不用付。
📋 Linux 上看真实页表开销:grep VmPTE /proc/<pid>/status——这就是这个进程的页表本身占了多少内存。
30 秒实验
grep -E 'VmSize|VmPTE' /proc/self/status,算页表占虚拟大小的比例。- 跑一个占 1 GB 内存的进程,再看它的 VmPTE 涨了多少。
cat /proc/cpuinfo | grep -o 'pdpe1gb',看 CPU 支不支持 1 GB 大页。
常见误区
- 【多级页表是为了让查表更快】。它是为了让页表本身小到能建得起;查表反而更慢,靠 TLB 补回来。
- 【页表级数越多越好】。级数是被地址位宽逼出来的,每多一级就多一次最坏情况的访存。
- 【页表项越少越省内存】。真正的省法是只给用到的地址段建表,不是减少每张表的项数。
自测三问(点开对答案)
1. 为什么每张页表正好占一页? —— **512 项 × 8 字节 = 4096 字节 = 4 KB**。**表的大小等于页大小,才能像普通页一样分配、换出、缓存。** 2. 四级页表凭什么能把 512 GB 变成几 MB? —— **只给【真正用到的地址段】建表**,**没用到的那 511 项后面整棵子树都不建。** 3. 多级页表查得慢,靠什么补回来? —— **TLB**——缓存最近的翻译结果,**命中就一次都不用查**。**没有 TLB,这个设计不可用。**- ☐ 5.17 标准大页
相关笔记
- 页表 — 单级为什么撑不住
- 标准大页 — 另一个减负方向
- 转译后备缓冲区 — 兜底的 TLB
- 🐧 Linux是怎样工作的 · 索引