🐧 第五章 内存管理
📄 一句话概括

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 秒实验

  1. grep -E 'VmSize|VmPTE' /proc/self/status,算页表占虚拟大小的比例。
  2. 跑一个占 1 GB 内存的进程,再看它的 VmPTE 涨了多少。
  3. cat /proc/cpuinfo | grep -o 'pdpe1gb',看 CPU 支不支持 1 GB 大页。

常见误区

  1. 【多级页表是为了让查表更快】。它是为了让页表本身小到能建得起;查表反而更慢,靠 TLB 补回来。
  2. 【页表级数越多越好】。级数是被地址位宽逼出来的,每多一级就多一次最坏情况的访存。
  3. 【页表项越少越省内存】。真正的省法是只给用到的地址段建表,不是减少每张表的项数。
自测三问(点开对答案) 1. 为什么每张页表正好占一页? —— **512 项 × 8 字节 = 4096 字节 = 4 KB**。**表的大小等于页大小,才能像普通页一样分配、换出、缓存。** 2. 四级页表凭什么能把 512 GB 变成几 MB? —— **只给【真正用到的地址段】建表**,**没用到的那 511 项后面整棵子树都不建。** 3. 多级页表查得慢,靠什么补回来? —— **TLB**——缓存最近的翻译结果,**命中就一次都不用查**。**没有 TLB,这个设计不可用。**

相关笔记