TLB(Translation Lookaside Buffer)是 MMU 的页表缓存。一条 TLB 项让 VA→PA 翻译从 “4 次内存读” 降到 “1 cycle”。但 TLB 容量几十到几千条,任何一次修改全局映射都可能要全核同步:TLB shootdown。
一、先看图
flowchart LR
CPU0[CPU0 mm->pgd 改] -->|写 PTE| PTE[页表]
CPU0 -->|调用 flush_tlb_mm/range| IPI{广播}
IPI -.发 IPI.-> CPU1
IPI -.发 IPI.-> CPU2
IPI -.发 IPI.-> CPU3
CPU1 --> INV1[本地 invlpg/TLBI]
CPU2 --> INV2[本地 invlpg/TLBI]
CPU3 --> INV3[本地 invlpg/TLBI]
INV1 --> ACK[ack 等回复]
INV2 --> ACK
INV3 --> ACK
ACK --> DONE[CPU0 继续]
classDef sw fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef sync fill:#f0883e22,stroke:#f0883e,color:#adbac7;
class CPU0,PTE sw
class IPI,ACK sync
二、TLB 为什么贵
TLB miss 成本:
- L1 dTLB 约 32-64 条(1 cycle)
- L2 TLB 约 1500-4000 条(7-20 cycle)
- 全 miss 走 page walk:每级读 1 次内存 + page walker cache 命中/miss
4 级 page walk 全 miss:100-400ns。相比 L1 cache 1 cycle,差 3 个数量级。
Huge page 把一条 TLB 项覆盖 512x 或 256x 地址,显著降低 miss 率。
三、TLB shootdown:多核的锅
场景:CPU0 改了一条 PTE(munmap、mprotect、swap-out、COW 等)。CPU1-N 的 TLB 可能还缓存旧翻译。必须让其他核作废对应条目。
x86 没有广播 TLB 指令(直到最近的 INVLPGB)——只能:
- CPU0 发 IPI
- 每核进入 IPI handler,本地
invlpg - ack 回 CPU0
- CPU0 这时才能真正复用物理页
瓶颈:核数越多,shootdown 的 O(N) 通信越贵;成百上千核的系统一次 munmap 可能卡几十 μs。
四、Linux 的优化
4.1 批量
一次操作多个页 → 合并一次 IPI,一次广播多条 invlpg。
4.2 范围 vs 全量
- 改 1-N 页:
flush_tlb_range(逐条 invlpg) - 改很多页:
flush_tlb_mm(整 mm 刷) - 阈值
tlb_flushall_shift,过阈值直接全刷
4.3 Deferred / mm_tlb_flush_pending
tlb_gather_mmu / tlb_finish_mmu
把多次 munmap 的刷合并到一次批处理。
4.4 Lazy TLB
内核线程没自己的 mm,借用上一个进程的
mm(active_mm)。这段期间若物主进程 exit 了但
lazy CPU 还”用”着,会推迟 mm 释放。Linux 6.x 有
lazy_tlb_mm_refcount 改造降低开销。
五、INVLPGB(AMD)
Zen 3+ 提供 INVLPGB
指令:广播无效化,不走
IPI,硬件级完成。
Linux 6.5+ 有针对性优化,在 AMD 支持的机器上自动切换到 INVLPGB 路径。效果:多核 TLB shootdown 延迟数量级下降。
Intel 没对等指令;2024 后有 “Remote Action Request”(RAR)硬件 hint 提案,尚未广泛。
六、arm64 TLBI
ARM 原生有广播 TLB 指令:
TLBI VAE1IS, x0 // by VA, EL1, Inner Shareable 广播
TLBI ASIDE1IS, x0 // by ASID
TLBI VMALLE1IS // 全部 EL1
IS 表示 Inner Shareable,硬件自动广播到所有共享域内的核。不需要 IPI。
这是 ARM 多核扩展性的先天优势——但也意味着软件要小心别过度 TLBI。
七、huge page 对 TLB 的放大
- 4K 页:一条 TLB 覆盖 4KB
- 2M huge:覆盖 2MB(x512)
- 1G huge:覆盖 1GB(x256K)
对 DB/JVM 大堆,打开 HugeTLB 或 THP,TLB miss 率可能掉 70-90%。但 fragmentation 和 compaction 开销要考虑,详见 D-40。
八、观察 TLB 行为
8.1 硬件计数器
perf stat -e dTLB-loads,dTLB-load-misses,\
dTLB-stores,dTLB-store-misses,\
iTLB-loads,iTLB-load-misses \
./app
8.2 TLB shootdown 计数
grep TLB /proc/interrupts # x86 的 IPI-TLB 行
TLB shootdowns行高速增长 = 高频 mprotect/munmap/swap
8.3 walk duration
perf stat -e dtlb_load_misses.walk_active,\
dtlb_load_misses.walk_completed ./app
Intel 上这两个事件能看出 TLB miss 占 CPU 多少周期。
九、典型 TLB 踩坑
A:频繁 mmap/munmap 小对象用 mmap
直接申请 → 持续 shootdown。用 malloc(arena 复用)或
madvise(MADV_DONTNEED) 代替
munmap(后者只释放物理页,不拆 VMA)。
B:大量 mprotect 切换 JIT 把代码页不断
RW/RX 切换导致刷 TLB。近代做法:memfd_secret
或双 map(一份 RW 一份 RX)。
C:THP 撕裂抖动 THP split 发生时多核同步刷,带来延迟毛刺。数据库类关 THP(never 或 madvise)。
D:NUMA 跨节点 shootdown 跨节点 IPI 贵;把任务/内存绑到同 NUMA。
十、设计建议
- 高吞吐场景优先选 arm64 或 AMD Zen3+(硬件广播 TLB)
- 大内存 DB:huge page + mlock + hugepages=静态预留
- JIT:双映射或 memfd_secret,别用 mprotect 在热路径
- 容器:避免 overlay 导致频繁 mmap 抖动
十一、小结
- TLB 是 VA 翻译的主缓存,miss 代价 3 个数量级
- shootdown 是多核扩展性的关键瓶颈;ARM 和新 AMD 有硬件广播
- Linux 有 batch / deferred / lazy tlb 多重优化
- huge page 是减 TLB miss 的主工具
参考文献
- Intel SDM Vol 3A §4.10 “Caching Translation Information”
- AMD APM Vol 2 §5.5
Documentation/arch/x86/tlb.rst- Villavieja et al. “DiDi: Mitigating the performance impact of TLB shootdowns.” PACT 2011
- Corbet, J. “Improving TLB shootdowns.” LWN.net 2021
- “Introducing INVLPGB instruction.” AMD, 2022
工具
perf stat -e dtlb_*/proc/interruptsbpftrace -e 'tracepoint:tlb:tlb_flush { @[args->reason]=count(); }'trace-cmd record -e tlb
上一篇:ARMv8 VMSA 页表 下一篇:mm_struct 与 VMA
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】HugeTLB 与 THP
大页能让一条 TLB 覆盖 2MB 乃至 1GB,但 THP 为什么在数据库里默认关掉?本文讲 HugeTLB 预留池、THP 的 khugepaged、defrag stall、madvise 模式、file-backed THP、以及工程上的取舍。
【操作系统百科】内存回收
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 集成。