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 查找的完整流程,从虚拟地址输入到物理地址输出:
从这张图可以看得很清楚: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 的虚拟化支持等等。这些我们后面会逐一展开。