3. 多级缓存架构:TLB与PTC的分级设计
说到SMMU的性能,缓存架构是绕不开的核心。我个人习惯把SMMU的缓存体系比作一个「三级火箭」——微TLB、主TLB、页表缓存PTC,每一级都有自己的使命。
你想想看,CPU发一个DMA请求过来,虚拟地址要转成物理地址。如果每次都去内存里翻页表,那延迟谁都受不了。所以SMMU必须用缓存来加速。但缓存怎么分级、每级存什么、命中率怎么平衡,这里面门道不少。
3.1 微TLB(Micro TLB)——第一道防线
微TLB,也叫ITLB(Instruction TLB)或DTLB(Data TLB)的微型版本。它位于SMMU流水线的最前端,紧挨着翻译引擎。
核心特点:
- 容量小:通常只有8~32个条目
- 全相联:查找速度快,一个时钟周期就能出结果
- 只缓存最近使用的映射:说白了就是热数据
我在项目中遇到过一个问题:某个GPU驱动频繁切换上下文,微TLB的命中率掉到了30%以下。结果就是每次翻译都要穿透到主TLB,延迟从1个周期飙升到10几个周期。后来我们做了个微TLB的上下文标记优化,命中率才回到80%以上。
实战建议:微TLB的条目数不是越多越好。我见过有人把微TLB做到128条,结果面积翻倍,时序反而跑不上去。一般来说,16~32条是性价比最高的区间。
3.2 主TLB(Main TLB)——中坚力量
微TLB没命中,就会走到主TLB。主TLB的容量大得多,通常是512~1024个条目,采用组相联结构。
| 特性 | 微TLB | 主TLB |
|---|---|---|
| 容量 | 8~32条目 | 512~1024条目 |
| 关联度 | 全相联 | 4~8路组相联 |
| 查找延迟 | 1周期 | 2~4周期 |
| 功耗 | 低 | 中等 |
| 替换策略 | LRU或FIFO | 伪LRU |
主TLB的设计有个关键点——页粒度。我记得有一次调试,发现主TLB的命中率波动很大。查了半天,原来是4KB小页和2MB大页混用,导致TLB条目被频繁替换。后来我们强制对大页做了专门的TLB分区,问题才解决。
避坑指南:我曾经在某个项目中,主TLB的替换策略用了最简单的FIFO。结果遇到一个流式访问模式,命中率惨不忍睹。后来换成伪LRU,命中率提升了15%。替换策略的选择,真的不能偷懒。
3.3 PTC(Page Table Cache)——最后的堡垒
如果主TLB也没命中,那就得走页表漫步(Page Table Walk)了。但页表漫步需要多次访问内存,延迟很高。这时候PTC就派上用场了。
PTC缓存的是页表中间级别的条目,而不是最终的物理地址。说白了,它存的是「页表的页表」。
PTC的分级结构:
- L1 PTC:缓存PGD/PUD级别的条目,容量最小,速度最快
- L2 PTC:缓存PMD级别的条目,容量中等
- L3 PTC:缓存PTE级别的条目,容量最大,但速度最慢
你可能会问:为什么不直接用主TLB覆盖所有情况?原因很简单——面积和功耗。主TLB做到1024条已经很大了,但页表可能有几百万条。PTC用更小的代价,缓存了页表漫步过程中的热点数据。
3.4 三级缓存的协同工作流程
下面这张图展示了整个翻译流程:
3.5 缓存一致性维护
多级缓存带来的最大挑战就是一致性。当软件修改了页表,硬件必须知道哪些缓存条目失效了。
我记得在一个项目中,驱动更新了页表但没有做TLB invalidation,结果DMA读到了旧数据。排查了整整两天才发现是缓存一致性问题。
关键机制:
- TLB Invalidation:通过SMMU的INVAL寄存器,可以按ASID、VA范围或全局方式刷TLB
- PTC Invalidation:页表修改后,需要同步刷PTC,否则页表漫步会读到旧数据
- 广播机制:在多SMMU系统中,一个SMMU的TLB刷需要广播到其他SMMU
3.6 性能调优实战要点
基于我多年的调试经验,这里给出几个调优方向:
| 调优项 | 方法 | 预期效果 |
|---|---|---|
| 微TLB命中率 | 增加条目数、优化上下文切换频率 | 减少主TLB访问次数 |
| 主TLB命中率 | 使用大页、优化替换策略 | 减少页表漫步次数 |
| PTC命中率 | 增加PTC容量、优化页表布局 | 减少内存访问延迟 |
| TLB刷效率 | 按ASID刷、批量刷 | 减少刷TLB的开销 |
我的经验:调优时不要只看单个缓存的命中率。我习惯用perf工具同时监控微TLB、主TLB和PTC的命中率,看整体流水线的延迟分布。有时候微TLB命中率低一点没关系,只要主TLB能兜住就行。