一、先看图
flowchart LR
VA[48/57 位 VA] --> PML5[PML5E<br/>LA57 开启]
PML5 --> PML4[PML4E]
PML4 --> PDPT[PDPTE<br/>1G page 可停]
PDPT --> PD[PDE<br/>2M page 可停]
PD --> PT[PTE<br/>4K]
PT --> PA[物理地址]
CR3[CR3 寄存器] --> PML4
classDef reg fill:#a371f722,stroke:#a371f7,color:#adbac7;
classDef tbl fill:#388bfd22,stroke:#388bfd,color:#adbac7;
class CR3 reg
class PML5,PML4,PDPT,PD,PT tbl
二、4 级分页(默认 48 位 VA)
VA 被切成 9+9+9+9+12:
bit 47..39 38..30 29..21 20..12 11..0
PML4 PDPT PD PT 偏移
每级 512 项 × 8 字节 = 4KB 一页(正好一个 page)。走 4 次内存解析一个 VA,MMU 硬件做,称 page walk。
CR3 指向当前进程的 PML4 物理地址;切换地址空间就是换 CR3。
三、5 级分页 LA57
2017 Ice Lake 开始硬件支持 57 位 VA(128PB)。上面多一级 PML5。需要:
- CPU 支持
la57feature - 固件/BIOS 开启
- 内核
CONFIG_X86_5LEVEL=y(现代发行版默认) - 单进程通过
mmap传超 47 位地址才会用到,否则兼容模式还是 48 位
查看:grep la57 /proc/cpuinfo。
四、大页停止
PDE 的 PSE bit=1 → 这一项不是再下一级表,而是直接 2MB 物理页。PDPTE 同理停 1GB。
所以 x86_64 页大小有 4K / 2M / 1G 三档。huge page 提高 TLB 命中、减少 page walk 成本。
五、PTE 的关键 bit
63 62..52 51..12 11..9 8 7 6 5 4 3 2 1 0
NX SW/MPK PA[51..12] SW G PAT D A PCD PWT U/S R/W P
- P:present;=0 表示未映射,访问即 #PF
- R/W:可写
- U/S:用户可访问
- PWT/PCD/PAT:缓存策略(WB/WT/UC/WC)
- A:accessed;硬件访问时自动置位,LRU 用
- D:dirty;写过就置位
- G:global;跨 CR3 保留 TLB(内核页用)
- NX (bit 63):XD,不可执行
- PKU bit 59..62:Protection Key(Skylake-X+)
- 硬件忽略的软件 bit:kernel 存放辅助信息(如 soft-dirty)
六、CR3 切换
一次 fork/exec 后任务切换:
mov %rax, %cr3 # rax = 新 mm->pgd 物理地址
副作用:刷光所有非-global TLB 项。这是进程切换最大开销之一。
七、PCID:TLB 按地址空间标签化
无 PCID 时每次换 CR3 都清 TLB。启用 PCID 后 CR3 低 12 bit 存 12-bit PCID,TLB 按 (PCID, VA) 查,换进程不用清。
Linux 4.14+
默认启用(X86_FEATURE_PCID),每进程分配
PCID(12 bit 所以只 4096 个,循环用)。
INVPCID
专门无效化某个 PCID 的 TLB 条目:
invpcid_flush_single_context(pcid);
invpcid_flush_single_addr(pcid, va);
invpcid_flush_all_nonglobals();
PTI(KPTI)把用户/内核地址空间分开两份页表 → PCID 的价值翻倍。
八、硬件 page walker 与 MMU cache
MMU 不只缓存 TLB,还缓存上层页表项:Paging-structure caches。
- PDE cache
- PDPE cache
- PML4E cache
所以即使 TLB miss,只要走到”第二级”就能命中上层缓存,省 2-3 次内存。
4 级 walk 若全 miss 要读 4 次 RAM(每次 ~100ns)= ~400ns 纯翻译开销。这是为什么 huge page + Intel ERMS 对某些负载立竿见影。
九、硬件辅助特性
- SMEP (bit 20 CR4):内核态不能执行用户页;阻挡 ret2usr
- SMAP (bit 21 CR4):内核态访问用户页需显式 stac/clac
- UMIP:阻止用户态读 GDTR/IDTR
- LA57:5 级分页开关
- CET:阴影栈
查看:/proc/cpuinfo 的
flags,关注
smep smap pcid invpcid la57 pku。
十、页表内存开销
一个进程 ≈ 多少页表内存?
- 每级表 4KB
- 48 位 VA、密集访问 1TB → 约 512K 条 PTE → PT 占 ~1GB
- 用 2MB huge page → 上升一级,~2MB
工作集 >100GB 的 DB 不用 huge page 能卡在 TLB 和 page table 开销上。
十一、观察
# 看进程页表大小
grep VmPTE /proc/$$/status
# 看 CPU 特性
grep -oE 'la57|pcid|invpcid|pku|smep|smap|nx' /proc/cpuinfo | sort -u
# x86 raw PTE dump(需 crash/drgn)
drgn -c /proc/kcore -e 'print(find_task(1234).mm.pgd)'十二、常见坑
A:Transparent PKU / MPK 混用时 fork 后子进程 pkey 可能丢——glibc 2.38+ 有改进 B:LA57 + 老 JIT:某些 JIT 假设 VA 高位 0,越界就崩 C:容器里 PCID/INVPCID 一般能用,但某些老 hypervisor 不透传
十三、小结
- 4 级分页覆盖 48 位,LA57 5 级覆盖 57 位
- PTE 的每个 bit 都是工程折衷
- PCID 让进程切换不清 TLB
- huge page 是减少 page walk 开销的主工具
- 下一篇同主题的 ARM 版,翻译体制差别很大
参考文献
- Intel SDM Vol 3A §4 “Paging”(最权威)
- AMD APM Vol 2 §5
Documentation/x86/x86_64/5level-paging.rst- Corbet, J. “Five-level page tables.” LWN.net 2017
- Henry de Valence, “Intel MPK and pkeys in practice.” 2019
工具
/proc/$$/status(VmPTE 行)drgn、crash- Intel
VTune、
perf stat -e dtlb_load_misses.walk_duration pmap -XX
上一篇:虚拟内存模型 下一篇:ARMv8 VMSA 页表
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。
【操作系统百科】用户态分配器
glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。