SMMU 多级缓存架构:TLB、微 TLB、主 TLB 与 PTW Cache
各位好,我是老吴。今天咱们聊聊 SMMU 的缓存体系。说实话,很多做系统软件的朋友,一开始都觉得 SMMU 就是个地址翻译器,没啥大不了的。但等你真正碰到性能瓶颈,比如 IO 延迟突然飙升、吞吐量上不去的时候,你才会发现——问题十有八九出在缓存上。
我当年调一个 NVMe 驱动的性能,死活差 30%。最后抓出来,是 SMMU 的微 TLB 频繁失效,导致每次 IO 都要穿透到主 TLB 甚至内存里去查页表。嗯,从那以后,我再也不敢小看这玩意儿了。
为什么需要多级缓存?
说白了,就是速度与容量的博弈。CPU 的 MMU 有 L1、L2 TLB,SMMU 也一样。设备发起的 DMA 请求,如果每次都要去内存里翻页表,那延迟是灾难性的。
你想想看,一个 PCIe Gen4 的设备,带宽几十 GB/s。如果每次地址翻译都要等几百纳秒,那整个 IO 路径就废了。所以,SMMU 必须在硬件上做多级缓存,把最常用的翻译结果存起来。
核心原则: 越靠近设备的缓存,速度越快,容量越小。越靠近内存的缓存,容量越大,速度越慢。
四层缓存结构总览
我个人习惯把 SMMU 的缓存体系分成四层。注意,不是所有硬件都实现了全部四层,但主流的高性能 SMMU(比如 ARM 的 SMMUv3.2)基本都齐了。
| 层级 | 名称 | 容量 | 延迟 | 存储内容 |
|---|---|---|---|---|
| L0 | 微 TLB (μTLB) | 8~64 条 | 1~2 周期 | 最近使用的 1~2 个页表条目 |
| L1 | 主 TLB (Main TLB) | 512~2048 条 | 3~5 周期 | 完整的页表条目(含权限、属性) |
| L2 | 页表缓存 (PTW Cache) | 8~64 KB | 10~20 周期 | 页表中间层级(L1/L2/L3 页表项) |
| L3 | 系统内存 (DRAM) | 无上限 | 100+ 周期 | 完整的页表结构 |
这张表你最好记下来。调优的时候,脑子里要时刻有这张图。
微 TLB (μTLB):第一道防线
微 TLB 是离设备最近的缓存。它很小,通常只有几十个条目。但它的速度极快,几乎不增加流水线延迟。
它的设计思路是:一个设备在短时间内,通常只访问连续的几页内存。比如一个网卡在收包,它可能连续写同一个 buffer 的不同偏移。这时候,微 TLB 就能完美命中。
我的经验: 微 TLB 的条目数通常与设备的 outstanding 请求数相关。如果你发现设备有大量乱序请求,微 TLB 太小就会频繁失效。我曾经在调试 GPU 驱动时,把微 TLB 从 16 条扩到 32 条,性能直接提升了 12%。
主 TLB (Main TLB):中坚力量
当微 TLB 失效时,请求会落到主 TLB。主 TLB 是真正的缓存主力。它存储的是完整的页表条目,包括物理地址、权限位、缓存属性、访问标志等。
主 TLB 通常采用全相联或组相联结构。我个人更倾向于组相联,因为它在面积和命中率之间取得了很好的平衡。
这里有个关键点:主 TLB 的条目是“合并”过的。什么意思?如果连续 4 个 4K 页映射到同一个 16K 大页,主 TLB 可以只存一条记录。这就是所谓的“块合并”或“超级条目”。
注意: 块合并不是自动的。它依赖于页表本身使用了大页(比如 2M、1G)。如果你在固件里配置的都是 4K 小页,那主 TLB 的命中率会大打折扣。我见过有人抱怨 SMMU 性能差,结果一看页表,全是 4K 页——这能快才怪。
页表缓存 (PTW Cache):隐藏的加速器
页表缓存,也叫 PTW Cache 或 Structure Cache。它不存最终的翻译结果,而是存页表本身的中间层级。
举个例子:一个 4 级页表查询,需要从内存里读 4 次。如果 PTW Cache 里已经缓存了 L1、L2 级的页表项,那硬件只需要读 L3 和 L4 两级。这能省掉一半的内存访问。
PTW Cache 的容量通常比主 TLB 大,但延迟也更高。它适合那些“页表结构复杂但变化不频繁”的场景。
我记得有一次,一个存储阵列的控制器,每次 IO 都要查 5 级页表(因为虚拟化嵌套)。没有 PTW Cache 的时候,延迟惨不忍睹。后来我们在 SMMU 固件里把 PTW Cache 开到最大,延迟直接降了 40%。
缓存之间的协作流程
下面这张图,是我自己画的 SMMU 缓存查询流程。你可以看到,一个地址翻译请求,是怎么一层层往下走的。
流程很简单:设备发起请求 → 查微 TLB → 命中则直接返回;失效则查主 TLB → 再失效则查 PTW Cache → 还失效就只能去内存里翻页表了。
每次命中,结果都会回填到上一级缓存。这叫“缓存填充”。所以,一次内存访问,可能会同时更新 PTW Cache、主 TLB 和微 TLB。
固件配置对缓存的影响
说了这么多,跟固件有什么关系?关系大了。SMMU 的缓存行为,很大程度上是由固件里的配置寄存器决定的。
- TLB 条目数: 很多 SMMU 允许固件配置主 TLB 的条目数。如果你知道设备的工作集很大,就把它设大一点。
- PTW Cache 大小: 这个通常跟页表级数有关。级数越多,PTW Cache 越有用。
- 缓存策略: 是写通还是写回?是 LRU 还是随机替换?这些都会影响命中率。
- 预取使能: 有些 SMMU 支持硬件预取。如果设备访问模式是顺序的,打开预取能大幅提升命中率。
我曾经踩过的坑: 有一次,我把 SMMU 的缓存策略配成了“写通”,结果每次 TLB 失效都会写回内存,导致总线拥堵。后来改成“写回”,问题解决。所以,别小看这些配置项,它们直接影响性能。
性能调优的核心指标
调优的时候,我主要看三个指标:
- TLB 命中率: 理想情况下,微 TLB 命中率应该在 90% 以上。如果低于 80%,说明工作集太大,或者页表配置不合理。
- PTW Cache 命中率: 这个应该在 95% 以上。如果频繁失效,说明页表结构变化太快,或者 PTW Cache 太小。
- 平均翻译延迟: 这个可以通过硬件计数器直接读到。如果延迟超过 50 个周期,就该检查缓存配置了。