TLB 基础:TLB 的作用、层次结构与查找过程

大家好,我是你们的老朋友。今天我们来聊聊 TLB——这个在 SMMU 里出镜率最高的部件。说实话,我见过不少工程师把 TLB 当成一个简单的“地址缓存”来理解,结果在调优时踩了坑。TLB 远不止是个缓存,它是整个地址翻译流水线的核心加速器。

TLB 的作用:为什么需要它?

先问一个问题:没有 TLB 会怎样?每次 CPU 访问内存,MMU 都要去查页表。页表在内存里,查一次页表就要读一次内存。如果页表有多级(比如 4 级页表),那就得读 4 次内存。一次内存访问几十纳秒,4 次就是上百纳秒。这还没算上真正的数据访问。

TLB 就是来解决这个问题的。它把最近用过的页表项(PTE)缓存起来。下次再访问同一个页面,直接查 TLB,一次搞定。命中延迟通常只有 1-2 个时钟周期,比查内存快两个数量级。

TLB 的核心价值:将多级页表查询(多次内存访问)转化为单次缓存查找,将地址翻译延迟从几十纳秒降低到纳秒级。

我在项目中遇到过一种情况:某款 SoC 的 TLB 命中率只有 85%,结果整个系统的内存延迟飙升了 3 倍。后来发现是 TLB 配置太小,加上工作集太大。说白了,TLB 就是地址翻译的“快照”,快照不够大,就得频繁去内存里翻页表。

TLB 的层次结构:L1 TLB 与 L2 TLB

现代处理器和 SMMU 的 TLB 都不是单层的。为什么?因为单层 TLB 很难同时满足“快”和“大”两个要求。你想想看,要快就得小,要小就装不下多少条目。要装得多就得大,大了就慢。

所以,业界普遍采用两级 TLB 结构:

特性 L1 TLB L2 TLB
容量 小(通常 32-64 条目) 大(通常 512-2048 条目)
延迟 1-2 个时钟周期 5-10 个时钟周期
关联度 全相联或高度组相联 组相联
功耗 低 较高
命中率 约 80-95%(取决于工作集) 累计 95-99%

L1 TLB 追求速度,所以做得小、做得快。它通常采用全相联结构,一次比较所有条目。L2 TLB 追求容量,做得大一些,用组相联结构来平衡速度和面积。

我记得有一次调试一个 IO 虚拟化场景,发现 DMA 性能波动很大。抓 trace 一看,L1 TLB 命中率只有 60%,但 L2 TLB 命中率有 98%。问题出在 L1 太小,频繁 miss 后去查 L2,虽然比查内存快,但延迟抖动还是影响了吞吐。后来我们调整了页表映射策略,把热页集中到连续地址空间,L1 命中率提升到了 85%。

实战建议:在 SMMU 场景下,L1 TLB 通常为每个设备流(Stream)独立分配,L2 TLB 则全局共享。设计时要注意 L1 的条目数是否能覆盖设备的热页工作集。

TLB 的查找过程:一步步拆解

TLB 的查找过程,说白了就是“拿着虚拟地址去 TLB 里找对应的物理地址”。但这里面有几个关键步骤,我带你走一遍。

步骤 1:提取 VPN

CPU 或设备发出一个虚拟地址(VA)。TLB 从 VA 中提取出虚拟页号(VPN)。VPN 就是 TLB 的查找键。页内偏移(Offset)不参与查找,直接透传。

步骤 2:TLB 查找

拿着 VPN 去 TLB 里匹配。如果是全相联 TLB,所有条目同时比较。如果是组相联 TLB,先根据 VPN 的一部分做索引,找到对应的组,再在组内比较。

步骤 3:命中 vs. 缺失

  • 命中(Hit):TLB 里找到了匹配的条目。直接读出物理页号(PPN),拼接页内偏移,得到完整的物理地址(PA)。同时检查权限位(读/写/执行)和属性位(缓存策略等)。
  • 缺失(Miss):TLB 里没找到。触发硬件页表遍历(Page Table Walk),去内存里查页表。查到后,把新的 PTE 填入 TLB,然后重试访问。

步骤 4:权限与属性检查

TLB 条目里不光有地址映射,还有权限位和属性位。比如这个页是否可读、可写、可执行,是否允许缓存,是否属于安全世界等。这些检查在 TLB 命中时同步完成,不额外消耗时间。

注意:TLB 缺失后的页表遍历是硬件自动完成的,不需要软件介入。但遍历过程会消耗多个内存访问周期。如果频繁缺失,性能会急剧下降。我曾经见过一个案例,某设备驱动分配了大量非连续内存,导致 TLB 缺失率高达 30%,性能直接腰斩。

TLB 查找流程全景图

下面这张图展示了 TLB 查找的完整流程,从虚拟地址输入到物理地址输出:

TLB 查找流程 TLB 查找流程全景图 虚拟地址 (VA) 提取 VPN(虚拟页号) L1 TLB 查找 命中:读出 PPN + 权限 缺失:触发页表遍历 拼接偏移 → 物理地址 L2 TLB 查找 L2 命中:填入 L1 + 返回 内存查页表 绿色 = 命中路径 红色 = 缺失路径

从这张图可以看得很清楚:L1 TLB 命中是最快的路径,直接出结果。L1 缺失后走 L2,L2 再缺失才去内存。每一级 miss 都会带来数量级的延迟增加。

TLB 的关联度与替换策略

TLB 的关联度直接影响命中率。常见的关联度有:

  • 全相联(Fully Associative):VPN 可以和任意条目比较。命中率最高,但硬件复杂,适合小容量 L1 TLB。
  • 组相联(Set Associative):VPN 先映射到某个组,只在组内比较。硬件简单,适合大容量 L2 TLB。
  • 直接映射(Direct Mapped):每个 VPN 只能映射到固定位置。硬件最简单,但冲突 miss 多,现在很少用。

替换策略方面,L1 TLB 常用 LRU(最近最少使用)或伪 LRU。L2 TLB 因为条目多,常用随机替换或 FIFO。我个人习惯在性能敏感场景下用 LRU,虽然硬件开销大一点,但命中率确实更高。

避坑指南:我曾经在调试一个 GPU 驱动时,发现 TLB 命中率突然下降。查了半天,原来是页表属性里的“全局位”没设置对,导致 TLB 刷得太频繁。记住:TLB 的维护(刷写、失效)和查找同样重要,后面我们会专门讲。

小结

TLB 是地址翻译的“快照缓存”,用两级结构平衡速度和容量。L1 追求快,L2 追求大。查找过程就是 VPN 匹配 + 权限检查 + 地址拼接。命中时纳秒级完成,缺失时触发页表遍历,代价高一个数量级。

嗯,这一节的内容就到这里。TLB 的细节还有很多,比如多级页表怎么和 TLB 配合、TLB 的 coherence 问题、SMMU 里 TLB 的虚拟化支持等等。这些我们后面会逐一展开。