内存管理:页表(译文)
本文译自 OSDev Wiki 上的一篇文章,原文链接为 Page Tables。
页面表(或页映射层级)用于将每个虚拟页映射到对应的物理页。零个或多个虚拟页可以对应同一个物理页。页的大小取决于处理器模式(保护模式、兼容模式或长模式)、所使用的扩展(如 PAE)以及处理器所支持的虚拟地址位数(当前 AMD64 处理器最多支持 48 位虚拟地址)。
Determining your page size
要确定理想的页面大小,不能只看分页结构本身的开销。
通常(对于用户空间),每个进程至少有 3 个具有不同特性的区域:可执行区域、只读且不可执行区域、可读写且不可执行区域。当使用分页来强制这些保护时,从每个区域的实际结尾到下一个页边界之间会产生填充。你可以假设这种填充平均相当于页大小的 50%。例如,对于 4096 字节页面和 3 个区域来说,你可以预计每个进程会因为填充浪费 6 KiB RAM;对于 2 MiB 页面,则每个进程预计浪费 3 MiB RAM。
通常(对于像 Windows 和 Linux 这样的操作系统),大约有 50 个进程,其中大多数使用非常少的 RAM,而一些使用很多(如 X、浏览器等)。如果假设 50 个进程每个平均使用 10 MiB RAM;那么(在长模式下使用 4 KiB 页时)每个进程(平均)会使用一个 PML4、一个 PDPT、一个 PD 和 5 个页表;即总共约 32 KiB 的分页结构。而对于 2 MiB 页面,每个进程(平均)会使用一个 PML4、一个 PDPT 和一个 PD;即总共约 12 KiB 的分页结构。
这给我们一些粗略的对比数据。对于 50 个进程,每个进程平均使用 10 MiB,且有 3 个不同的区域:
- 4 KiB 分页的开销约为:填充 6 KiB + 分页结构 32 KiB,即每个进程约 38 KiB。
- 2 MiB 分页的开销约为:填充 3 MiB + 分页结构 12 KiB,即每个进程约 3084 KiB。
- 4 KiB 分页对于全部 50 个进程的总开销约为 1.86 MiB。
- 2 MiB 分页对于全部 50 个进程的总开销约为 150.6 MiB。
若所有进程实际使用 500 MiB RAM:
- 总开销 1.86 MiB 相当于多消耗约 0.37%,几乎可以忽略。
- 总开销 150.6 MiB 相当于多消耗约 23.15%,非常可观。
对于性能(例如 TLB 未命中),很难估计可能的成本,因为这取决于分页结构有多少保留在缓存中(以及有多少必须从 RAM 中取回)、TLB 的大小(小页与大页的 TLB 都算)、CPU 是否缓存更高层级的结构(现代 CPU 会这样做)、每个进程的工作集和访问模式、进程切换频率等……
然而(对于典型的操作系统和典型负载),我会假设使用 2 MiB 页带来的性能收益非常不可能值得为此浪费大约 23% 的额外 RAM。
基本上,4 KiB 页(带四级分页结构)逐渐显得稍微有些小,但下一步跳到 2 MiB 页(带三级分页结构)在大多数情况下又太大,不太现实。
为了减少分页层级,更好的方案是同时增大页目录、PDPT 等结构的大小。例如,对于 55 位虚拟地址,你可以使用 64 KiB 页、64 KiB 页表、64 KiB 页目录和 64 KiB PDPT。然而,我们需要等待 Intel(或 AMD)做出类似的设计。
~ Brendan
幸运的是,你可以自由混合使用 4 KiB、2 MiB 和 1 GiB 页。你无需对所有内容使用统一的页大小。因此,一个拥有 9MB 数据的进程可以使用 4 个 2 MiB 页,剩余部分再用 4 KiB 页补齐。这节省了分页结构、改善了 TLB 利用率,同时不会增加额外开销。
递归映射(Recursive mapping)
为了更方便地修改当前地址空间的页映射(page map),你可以把最高级页映射层级的一个条目映射到它自身。
递归映射会浪费一些虚拟地址空间。下表显示了递归映射页表所使用的相对空间:
| Mode | Page size | Max page map size | Used virtual space | Total virtual space | Ratio |
|---|---|---|---|---|---|
| Protected mode (non-PAE) | 4 KiB | 4 MiB | 4 MiB | 4 GiB | 1/1024 (0.1%) |
| 4 MiB | 4 KiB | ||||
| Protected mode (PAE) | 4 KiB | 8 MiB | 1 GiB | 4 GiB | 1/4 (25%) |
| 2 MiB | 16 KiB | ||||
| Long mode (48-bit) | 4 KiB | 512 GiB | 512 GiB | 256 TiB | 1/512 (0.2%) |
| 2 MiB | 1 GiB | ||||
| 1 GiB | 2 MiB |
下表中的 “Recursive mapping” 列展示了当页映射被作为最后一项递归映射时,用于访问特定页映射层级的基地址与偏移。
Protected/compatibility mode (32-bit) page map
在保护模式下,无论是否启用 PAE,虚拟地址空间的大小都是 32 位(4 GiB)。
Non-PAE mode
未启用 PAE 时,页映射最多可引用 32 位(4 GiB)的物理页地址。
4 KiB pages
| Level | Table | Size | Range | Bits | Entries | Pages | Recursive mapping |
|---|---|---|---|---|---|---|---|
| 0 | (page) | - | 0x1000 (4 KiB) | 12 bits | - | 0x1 (1) | - |
| 1 | PT | 0x1000 (4 KiB) | 0x40 0000 (4 MiB) | 10 bits | 1024 | 0x400 (1024) | 0xFFC0 0000 + 0x1000 * PDi |
| 2 | PD | 0x1000 (4 KiB) | 0x10000 0000 (4 GiB) | 10 bits | 1024 | 0x10 0000 (1048576) | 0xFFFF F000 |
4 MiB pages
| Level | Table | Size | Range | Bits | Entries | Pages | Recursive mapping |
|---|---|---|---|---|---|---|---|
| 0 | (page) | - | 0x40 0000 (4 MiB) | 22 bits | - | 0x1 (1) | - |
| 2 | PD | 0x1000 (4 KiB) | 0x10000 0000 (4 GiB) | 10 bits | 1024 | 0x400 (1024) | 0xFFC0 0000 |
PAE mode
启用 PAE 后,页映射最多可引用 36 位(64 GiB)的物理页地址。
4 KiB pages
| Level | Table | Size | Range | Bits | Entries | Pages | Recursive mapping |
|---|---|---|---|---|---|---|---|
| 0 | (page) | - | 0x1000 (4 KiB) | 12 bits | - | 0x1 (1) | - |
| 1 | PT | 0x1000 (4 KiB) | 0x20 0000 (2 MiB) | 9 bits | 512 | 0x200 (512) | 0xC000 0000 + 0x20 0000 * PDi + 0x1000 * PTi |
| 2 | PD | 0x1000 (4 KiB) | 0x4000 0000 (1 GiB) | 9 bits | 512 | 0x40000 (262144) | 0xC060 0000 + 0x1000 * PDi |
| 3 | PDP | 0x20 (32 bytes) | 0x10000 0000 (4 GiB) | 2 bits | 4 | 0x10 0000 (1048576) | 0xC060 3000 |
2 MiB pages
| Level | Table | Size | Range | Bits | Entries | Pages | Recursive mapping |
|---|---|---|---|---|---|---|---|
| 0 | (page) | - | 0x20 0000 (2 MiB) | 21 bits | - | 0x1 (1) | - |
| 2 | PD | 0x1000 (4 KiB) | 0x4000 0000 (1 GiB) | 9 bits | 512 | 0x200 (512) | 0xC000 0000 + 0x20 0000 * PDi |
| 3 | PDP | 0x20 (32 bytes) | 0x10000 0000 (4 GiB) | 2 bits | 4 | 0x800 (2048) | 0xC060 0000 |
Long mode (64-bit) page map
在长模式中,虚拟地址空间理论上可以是 64 位(16 EiB),但实际处理器只允许访问其中的一部分。目前最常见的处理器实现允许 48 位(256 TiB)的虚拟地址空间。对于这种虚拟地址,位 48–63 必须是位 47 的复制(类似符号扩展),这会将虚拟地址空间分为上半部分和下半部分。
48-bit virtual address space
4 KiB pages
| Level | Table | Size | Range | Bits | Entries | Pages | Recursive mapping |
|---|---|---|---|---|---|---|---|
| 0 | (page) | - | 0x1000 (4 KiB) | 12 bits | - | 0x1 (1) | - |
| 1 | PT | 0x1000 (4 KiB) | 0x20 0000 (2 MiB) | 9 bits | 512 | 0x200 (512) | 0xFFFF FF80 0000 0000 + 0x4000 0000 * PDPi + 0x20 0000 * PDi + 0x1000 * PTi |
| 2 | PD | 0x1000 (4 KiB) | 0x4000 0000 (1 GiB) | 9 bits | 512 | 0x40000 (262144) | 0xFFFF FFFF C000 0000 + 0x20 0000 * PDPi + 0x1000 * PDi |
| 3 | PDP | 0x1000 (4 KiB) | 0x80 0000 0000 (512 GiB) | 9 bits | 512 | 0x800 0000 (134217728) | 0xFFFF FFFF FFE0 0000 + 0x1000 * PDPi |
| 4 | PML4 | 0x1000 (4 KiB) | 0x10000 0000 0000 (256 TiB) | 9 bits | 512 | 0x10 0000 0000 (68719476736) | 0xFFFF FFFF FFFF F000 |
2 MiB pages
| Level | Table | Size | Range | Bits | Entries | Pages | Recursive mapping |
|---|---|---|---|---|---|---|---|
| 0 | (page) | - | 0x20 0000 (2 MiB) | 21 bits | - | 0x1 (1) | - |
| 2 | PD | 0x1000 (4 KiB) | 0x4000 0000 (1 GiB) | 9 bits | 512 | 0x200 (512) | 0xFFFF FF80 0000 0000 + 0x4000 0000 * PDPi + 0x20 0000 * PDi |
| 3 | PDP | 0x1000 (4 KiB) | 0x80 0000 0000 (512 GiB) | 9 bits | 512 | 0x40000 (262144) | 0xFFFF FFFF C000 0000 + 0x20 0000 * PDPi |
| 4 | PML4 | 0x1000 (4 KiB) | 0x10000 0000 0000 (256 TiB) | 9 bits | 512 | 0x8000000 (134217728) | 0xFFFF FFFF FFE0 0000 |
1 GiB pages
| Level | Table | Size | Range | Bits | Entries | Pages | Recursive mapping |
|---|---|---|---|---|---|---|---|
| 0 | (page) | - | 0x4000 0000 (1 GiB) | 30 bits | - | 0x1 (1) | - |
| 3 | PDP | 0x1000 (4 KiB) | 0x80 0000 0000 (512 GiB) | 9 bits | 512 | 0x200 (512) | 0xFFFF FF80 0000 0000 + 0x4000 0000 * PDPi |
| 4 | PML4 | 0x1000 (4 KiB) | 0x10000 0000 0000 (256 TiB) | 9 bits | 512 | 0x40 000 (262144) | 0xFFFF FFFF C000 0000 |