知行LEARNING HANDBOOK
目录 · N07 x86-64 页表项:64 位 Entry、物理地址位宽与标志位
学习手册/计算机基础/虚拟内存与页表
N0712 分钟更新于 2026-08-20

x86-64 页表项:64 位 Entry、物理地址位宽与标志位

拆解 x86-64 的 64 位页表项,区分物理地址字段、标志位与页大小差异。

x86-64page-tablephysical-addressPTEPML4EMAXPHYADDR

建议先了解

  • 二进制与十六进制
  • 地址、指针与内存的基本概念
  • 页与 4 KiB 对齐

1. 这篇笔记解决什么问题

这篇笔记集中解决三个容易混淆的问题:

  1. 物理地址到底是多少位?它是否由机器实际安装的 RAM 容量决定?
  2. 为什么 x86-64 的页表项固定是 64 bit,而机器的物理内存大小可以变化?
  3. 一个 64-bit 页表项中,地址和 Present、R/W、U/S、NX 等控制位是怎样同时保存的?

它位于计算机系统中的“虚拟内存 → 地址翻译 → 页表”知识链上。理解本篇后,应能够解释:

  • 物理地址空间(physical address space)与实际 RAM 容量为什么不是一回事;
  • MAXPHYADDR 的含义;
  • 为什么 4 KiB 对齐使页表项不必存储物理地址最低 12 bit;
  • 非叶子页表项(例如 PML4E)与叶子页表项(PTE)的区别;
  • ReservedIgnored 为什么不能混为一谈。

核验说明:本笔记中的架构性结论按当前 x86-64 文档口径整理,并与 Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, Chapter 4 以及 AMD64 Architecture Programmer’s Manual Volume 2: System Programming 的页表描述交叉核对。具体 CPU 支持的地址位宽和可选特性仍应以该型号的 CPUID 为准。

2. 前置知识

需要先理解:

  • 二进制 bit 编号,例如 bits 51:12
  • 十六进制与二进制之间的对应关系;
  • 物理地址与虚拟地址是不同的地址空间;
  • 对齐(alignment):4 KiB 对齐意味着地址最低 12 bit 必须为 0。

待补充:若继续深入 PWT、PCD、PAT,需要进一步学习 x86 的内存类型与缓存属性体系。

3. 核心概念

3.1 物理地址空间(Physical Address Space)

物理地址空间是 CPU/平台能够用物理地址标识的地址范围。

它不等同于“实际装了多少 RAM”。

例如,若某 CPU 支持 48-bit 物理地址:

248 bytes=256 TiB2^{48}\text{ bytes}=256\text{ TiB}

这表示 CPU 的物理地址最多可以描述 256 TiB 的地址范围,但机器完全可以只安装 32 GiB 或 64 GiB RAM。

物理地址空间中还可以出现:

  • DRAM;
  • PCIe 设备的 MMIO 区域;
  • 固件保留区;
  • APIC 等硬件映射;
  • 未映射、不可访问的空洞。

因此:

physical-address widthlog2(installed RAM)\text{physical-address width} \neq \log_2(\text{installed RAM})

3.2 最大物理地址位宽(MAXPHYADDR)

在 x86-64 中,具体处理器支持多少位物理地址由 CPU 架构实现决定,并通过 CPUID 枚举给软件。

通常将该位宽记为:

M=MAXPHYADDRM=\text{MAXPHYADDR}

现代 x86-64 页表格式能够容纳最高到 52-bit 的物理地址字段,但具体处理器的 MAXPHYADDR 可以小于 52。

例如:

  • M = 48:有效物理地址位为 47:0
  • M = 52:有效物理地址位为 51:0

3.3 页框(Page Frame)

4 KiB 页的大小为:

4096=212 bytes4096=2^{12}\text{ bytes}

因此一个 4 KiB 对齐的物理页框基址一定满足:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 000000000000
                                                    └──── 12 ────┘

最低 12 bit 恒为 0。

页表项没有必要重复保存这些 0,只需要保存物理地址的高位部分。

3.4 页表项(Page-Table Entry)

在 IA-32e/x86-64 的常见分页结构中,一个 paging-structure entry 固定为:

64 bit=8 bytes64\text{ bit}=8\text{ bytes}

64 bit 中同时容纳:

  1. 下一层页表或最终物理页的物理地址高位;
  2. 页表权限和状态位;
  3. 一些保留位、忽略位或可选功能位。

3.5 非叶子页表项与叶子页表项

非叶子页表项(non-leaf entry)指向下一层页表

例如 4-level paging 中:

PML4E → PDPT
PDPTE → PD
PDE   → PT

当不使用 huge page 时,最后一级:

PTE → 4 KiB physical page

PTE 是叶子页表项(leaf entry)。

另外:

  • PDPTE 可以在 PS=1 时直接映射 1 GiB 页;
  • PDE 可以在 PS=1 时直接映射 2 MiB 页。

4. 直观理解

可以把一个 64-bit 页表项理解为一个“压缩后的、带元数据的物理指针”。

普通 64-bit 指针似乎需要完整保存地址,但页表结构知道目标一定按 4 KiB 对齐,所以最低 12 bit 必定为 0。

于是原本:

physical address
[ 高位地址 ................................ ][000000000000]
                                               12 bits

可以改成:

page-table entry
[ 地址高位 ................................. ][flags]

这不是说地址“变短了”,而是说最低 12 个 0 是由对齐规则隐式恢复的。

类比边界:页表项并不是 C 语言意义上的普通指针。CPU 对每一位都有架构定义,并且错误设置保留位可能触发异常。

5. 工作原理

5.1 为什么一张页表恰好有 512 个 entry

一张常规页表占一个 4 KiB page:

4096 bytes4096\text{ bytes}

每个 entry 为:

8 bytes8\text{ bytes}

所以:

4096/8=512=294096/8=512=2^9

因此每一级页表索引需要 9 bit。

5.2 地址字段为什么从 bit 12 开始

若物理地址位宽为 M = MAXPHYADDR,对于指向下一张 4 KiB 对齐页表的 entry:

有效地址位:bits M-1 : 12
隐含为 0 :bits 11 : 0

恢复完整页表基址时,本质上是:

next_table_base=entry-address-field12\text{next\_table\_base} = \text{entry-address-field} \ll 12

或者从完整 64-bit entry 中做掩码:

base=entry & (((1M)1) & 0xFFF)\text{base} = \text{entry} \ \&\ \left(((1\ll M)-1)\ \&\ \sim 0xFFF\right)

5.3 4-level paging 中 PML4E 的基本结构

PML4E(Page-Map Level-4 Entry)是 L4 entry。

对于基础 4-level IA-32e page walk,可按下面的结构理解:

bit含义
0P:Present
1R/W:Read/Write
2U/S:User/Supervisor
3PWT:Page-Level Write-Through
4PCD:Page-Level Cache Disable
5A:Accessed
6硬件忽略位
7Reserved;PML4E 没有 PS 字段,必须满足架构要求
11:8硬件忽略/软件可用语义需结合架构与 OS
M-1:12下一层 PDPT 的物理地址高位
51:MM < 52 时属于保留地址位,不能当成有效物理地址
62:52基础语义下多为 ignored / feature-dependent
63XD/NX:Execute Disable,是否可用取决于 NXE 等特性

关键点不是背每一位,而是理解三类位:

[ 功能/扩展位 ][ 物理地址高位 ][ 状态与权限 flags ]

[!WARNING] “Ignored” 位是否能被操作系统拿来保存软件状态,需要结合具体 CPU 特性和内核定义判断,不能因为硬件在某种模式下忽略就默认“永远可自由使用”。

5.4 最终一级 PTE 的基本结构

4 KiB PTE 与 PML4E 最大的区别是:

PTE 的地址字段指向最终 physical page,而不是下一张页表。

常见关键位包括:

bit含义
0P:Present
1R/W:Read/Write
2U/S:User/Supervisor
3PWT
4PCD
5A:Accessed
6D:Dirty
7PAT(对 4 KiB PTE)
8G:Global
11:9架构忽略/软件使用区域之一
M-1:124 KiB physical page 的物理基址高位
51:MM < 52 时为保留地址位
62:59可由 Protection Keys 等特性解释,否则依具体配置
63XD/NX,是否有效取决于相关特性

5.5 L3/L2 的 PS 与 huge page

在常规 4-level paging 中:

PML4E
  ↓
PDPTE -- PS=0 --> PDE
   │
   └-- PS=1 --> 1 GiB page

PDE   -- PS=0 --> PTE
   │
   └-- PS=1 --> 2 MiB page

PTE ------------> 4 KiB page

一个重要纠正:

PML4E 没有 PS 字段。

不能把“PML4E 的 bit 7 必须为 0”简单表述成“PML4E 的 PS=0”;更准确的说法是该位置在 PML4E 格式中是保留位。

6. 示例

示例 1:为什么 52-bit 物理地址只需要保存 40 个地址 bit

假设:

MAXPHYADDR = 52
page size  = 4 KiB

完整物理地址有效范围是:

bits 51:0

但页框最低 12 bit 恒为 0,因此页表项只需存:

bits 51:12

位数为:

5112+1=4051-12+1=40

恢复时:

stored 40-bit page-frame number
            ↓ << 12
52-bit physical page base

因此“只存 40 bit”并不意味着物理地址只有 40 bit。

示例 2:一个非叶子 entry

假设下一张页表的物理基址是:

0x0000000000200000

并设置:

P  = 1
RW = 1
US = 1

最低三位即:

111₂ = 0x7

于是一个简化示例 entry 可以写成:

0x0000000000200007

其中:

0x0000000000200000   ← 物理页表基址
0x0000000000000007   ← flags

提取基址时得到:

0x0000000000200000

示例 3:CPU 支持 48-bit 物理地址时

若:

MAXPHYADDR = 48

真正有效的物理地址字段是:

bits 47:12

不是 51:12 全部都能用于地址。

因此:

bits 51:48

不能因为 64-bit entry 中“有位置”就拿来扩展地址;它们必须满足该 CPU 架构定义的保留位要求。

7. 代码、命令或公式

7.1 通用的页框基址掩码

若:

M = MAXPHYADDR

则 4 KiB 页框地址掩码可以写成:

(((1 << M) - 1) & ~0xFFF)

例如概念性 Python:

def page_base_from_entry(entry: int, maxphyaddr: int) -> int:
    mask = ((1 << maxphyaddr) - 1) & ~0xFFF
    return entry & mask

这段代码只演示地址位提取,不负责检查 Present、Reserved、NX、Protection Key 等架构语义。

7.2 512 entries 的来源

4096 B8 B=512=29\frac{4096\text{ B}}{8\text{ B}}=512=2^9

7.3 Linux 查看 CPU 报告的地址位宽

可在 Linux 上尝试:

lscpu

常见输出中会包含类似 “Address sizes” 的信息。

不同发行版和 util-linux 版本的显示格式可能不同。

8. 容易混淆的概念

概念 A概念 B核心区别
物理地址位宽RAM 容量前者是 CPU/平台可描述的地址范围,后者是实际安装的存储器容量
物理地址空间DRAM物理地址空间还可包含 MMIO、保留区和空洞
页表项自身地址页表项中保存的地址前者是该 8-byte entry 存在哪里;后者是它指向下一张页表或物理页的位置
PML4EPTEPML4E 通常指向下一层 PDPT;4 KiB PTE 指向最终 physical page
ReservedIgnoredReserved 必须满足架构规定;Ignored 表示硬件在该语义下不使用,但软件用途仍需按架构/OS 规则
52-bit 物理地址40-bit 页框号4 KiB 对齐使最低 12 bit 隐含为 0,因此只需存高 40 bit

9. 常见误区

误区:物理地址位数由实际 RAM 容量直接决定

错误原因:

把“可寻址范围”与“实际安装容量”看成同一个概念。

正确理解:

物理地址宽度首先由 CPU 架构实现及平台能力决定。机器可以只安装物理地址空间中的一小部分 RAM。

如何验证:

查看 CPU 架构报告的 physical address bits,再与机器实际内存容量对比,两者通常不是简单的 log2(RAM) 关系。

误区:换一根更大的内存条,页表 entry 格式也必须变化

错误原因:

误以为页表格式按照当前 RAM 容量动态设计。

正确理解:

x86-64 paging entry 的宽度和字段布局是架构规定的。更换 RAM 只会改变哪些物理地址实际对应 DRAM,不会把 64-bit PTE 改成另一种宽度。

如何验证:

同一 CPU 平台在不同内存容量配置下仍使用同一 ISA 页表格式。

误区:PML4E 中存了完整的 52-bit 物理地址

错误原因:

忽略了 4 KiB 对齐。

正确理解:

最低 12 bit 恒为 0,页表项只保存 M-1:12 的地址位;低位由硬件隐式补 0。

如何验证:

观察任何合法 4 KiB 页表基址,其十六进制最低 3 位必为 000

误区:PML4E 也有一个 PS 位,只不过 PS=0

错误原因:

把 PDE/PDPTE 的 huge-page 格式机械套到 PML4E。

正确理解:

PML4E 不定义 PS 字段;对应位置是保留位,应按架构要求处理。

如何验证:

查阅 Intel SDM Volume 3A Chapter 4 或 AMD64 APM Volume 2 中 PML4E 的正式位布局。

误区:Ignored 位等于“操作系统想写什么就写什么”

错误原因:

忽略了 CPU 可选特性、未来架构扩展和 OS 自身编码约定。

正确理解:

“Ignored by hardware” 只说明在特定模式/功能组合下硬件 page walk 不使用该位。软件能否安全复用必须结合具体架构和内核实现。

如何验证:

对照对应 CPU 手册和操作系统的页表宏定义,而不是只看一张简化图。

10. 与其他知识的关系

本主题直接连接到地址翻译过程:

x86-64 地址翻译与四级页表遍历

继续向上游需要理解:

继续向下游会涉及:

11. 可以亲手完成的验证

实验目标

验证“CPU 支持的物理地址位宽”与“实际 RAM 容量”不是同一概念,并练习页框地址掩码。

所需环境

  • 一台 x86-64 Linux 机器;
  • Python 3;
  • 普通用户权限即可。

操作步骤

  1. 执行:

    lscpu
    
  2. 找到 physical address bits 或 “Address sizes” 类字段。

  3. 查看机器 RAM:

    free -h
    
  4. 比较两者。

  5. 用 Python 手动提取一个 entry 的页框基址:

    entry = 0x0000000000200007
    maxphyaddr = 52
    
    mask = ((1 << maxphyaddr) - 1) & ~0xFFF
    page_base = entry & mask
    
    print(hex(page_base))
    

预期现象

最后的 Python 输出应为:

0x200000

同时,CPU 报告的可寻址物理范围通常远大于机器实际安装的 RAM。

如何判断结果

如果 0x200007 中的最低 flags 被掩掉后得到 0x200000,说明已经理解了“地址高位 + flags”的编码方式。

实验不能证明什么

这个实验不能证明:

  • 该虚构 entry 真的存在于当前系统页表中;
  • 当前 Linux 内核如何使用所有 ignored bits;
  • CPU 内部 page walker 的微架构细节。

12. 尚未解决的问题

  1. 当前具体 CPU 型号的 MAXPHYADDR 到底是多少,需要读取该机器的 CPUID 或系统报告。
  2. 5-level paging(LA57)启用后,虚拟地址分解会增加一级 PML5;本篇没有展开。
  3. CR3 低 12 bit 在 PCID 开启/关闭时含义不同,本篇只讨论页表基址这一核心部分。
  4. PWT、PCD、PAT 与 MTRR 如何共同决定内存类型,需要单独整理。
  5. 操作系统如何复用硬件 ignored bits,需要结合 Linux/Windows 等具体内核实现分析。
  6. Protection Keys、NX、CET 等可选机制会改变部分高位的解释,需要按 CPU 功能组合查官方手册。

13. 自测问题

  1. 为什么 64 GiB RAM 并不意味着 CPU 的物理地址一定只有 36 bit?
  2. MAXPHYADDR=52 且页大小为 4 KiB 时,页表项需要显式保存多少个物理页框地址 bit?
  3. 为什么一个合法的 4 KiB 页表基址最低 12 bit 必须是 0?
  4. PML4E 与 4 KiB PTE 在“地址字段指向什么”这一点上有什么区别?
  5. ReservedIgnored 的区别是什么?
  6. 为什么 0x0000000000200007 可以同时表达一个页表地址和三个低位 flag?
  7. 为什么不能说“PML4E 的 PS=0”而应该更精确地描述 bit 7?
  8. MAXPHYADDR=48 时,页表项中的 bits 51:48 为什么不能当成额外的物理地址位?
参考答案
  1. 物理地址位宽描述 CPU/平台可寻址范围;实际 RAM 只是该范围中被 DRAM 占用的一部分,物理地址空间还可包含 MMIO、空洞和保留区。
  2. 52 - 12 = 40 bit,即 bits 51:12
  3. 4 KiB = 2^12,4 KiB 对齐地址一定是 4096 的整数倍,因此最低 12 bit 为 0。
  4. PML4E 通常指向下一层 PDPT;4 KiB PTE 指向最终映射的 physical page。
  5. Reserved 位必须满足架构规定;Ignored 位表示硬件在相应语义下不使用,但软件是否能复用仍要看具体架构和 OS。
  6. 目标页表基址最低 12 bit 本来就是 0,因此低位可以用于 flags;0x7 表示最低三位均为 1。
  7. PML4E 不定义 huge-page PS 字段;对应 bit 位置在该格式中是保留位。
  8. 因为有效物理地址只到 bit 47;高于 MAXPHYADDR-1 的地址位必须满足架构的保留位规则。

14. 一句话总结

x86-64 的 64-bit 页表项利用 4 KiB 对齐带来的 12 个隐含低零位,把物理页框地址与权限/状态信息压在同一个 entry 中;而物理地址位宽由 CPU 的 MAXPHYADDR 决定,并不等于机器实际安装的 RAM 容量。