3、SMMU硬件架构:内部模块、StreamID与SubstreamID、Context Bank与STE、TLB与微页表

好,咱们今天来聊聊SMMU的硬件架构。说实话,很多搞虚拟化的朋友,一听到SMMU就觉得头大,觉得这东西太底层、太复杂。其实你把它拆开来看,核心就那么几个模块。我当年第一次调SMMU驱动的时候,也是对着几百页的Spec发懵,后来发现,只要把StreamID、Context Bank、STE这几个概念理清楚,整个架构就豁然开朗了。

3.1 SMMU内部模块概览

SMMU不是一块铁板,它内部是由多个功能单元协同工作的。我个人习惯把它分成三大块:前端输入处理地址翻译核心后端输出接口

  • 前端(TCU - Translation Control Unit):负责接收来自设备端的访问请求,解析StreamID和SubstreamID,然后去查找对应的STE(Stream Table Entry)。
  • 核心(TLB + 微页表):这是翻译的主战场。TLB负责缓存最近用过的页表项,微页表则是在TLB Miss时,去内存里逐级查找页表。
  • 后端(TBU - Translation Buffer Unit):负责将翻译后的物理地址发往总线,同时处理权限检查、属性合并等。

核心要点:SMMU本质上就是一个专用的、硬件加速的页表遍历引擎。它跟CPU的MMU很像,但专门为IO设备做了优化。

下面这张图是我用SVG画的,能帮你快速建立整体印象:

IO设备 请求 前端 (TCU) 解析StreamID 查找STE STE指针 核心 (TLB + 微页表) TLB缓存 页表遍历 权限检查 物理地址 后端 (TBU) 属性合并 总线接口 内存 页表遍历(TLB Miss时) 内存中的页表

个人经验:我在调试一个NVMe SSD的虚拟化直通问题时,发现性能上不去。后来用trace工具一看,TLB Miss率高达30%。说白了就是StreamID分配得太散,导致TLB频繁失效。后来我把同一种类型的设备绑到连续的StreamID上,Miss率直接降到5%以下。

3.2 StreamID与SubstreamID

StreamID是SMMU世界里最核心的标识符。每个IO设备发出来的请求,都必须带上一个StreamID。SMMU根据这个ID,去查找对应的翻译规则。

你想想看,一个PCIe设备可能有多个功能(Function),每个功能还能有多个队列(Queue)。如果每个队列都想有自己的地址空间,怎么办?这时候SubstreamID就派上用场了。

  • StreamID:标识一个设备或设备功能。通常是PCIe的Bus/Device/Function号编码而来。
  • SubstreamID:标识设备内部的某个子流。比如一个NVMe控制器下的不同队列对。

关键点:StreamID + SubstreamID 的组合,构成了一个唯一的 PASID(Process Address Space ID)。这样,一个设备就能同时访问多个进程的地址空间了。

我记得有一次,客户反馈说他们的GPU虚拟化方案里,多个虚拟机共享同一个物理GPU,但地址翻译总是出错。我排查后发现,问题出在SubstreamID的分配上——两个VM的队列用了相同的SubstreamID,导致SMMU把它们的地址空间搞混了。嗯,这里要注意,SubstreamID的分配必须保证在同一个StreamID下是唯一的。

3.3 Context Bank与STE

STE(Stream Table Entry)是SMMU的配置核心。每个StreamID对应一个STE,STE里存放了该设备使用的页表基地址、地址空间大小、权限属性等信息。

但STE本身是存在内存里的,SMMU不能每次都去内存里读吧?那太慢了。所以就有了Context Bank

Context Bank是SMMU内部的一组寄存器缓存,用来存放最近用过的STE。你可以把它理解成STE的TLB。当设备发起请求时,SMMU先查Context Bank,命中就直接用;没命中,再去内存里读STE,然后加载到Context Bank里。

组件 位置 作用 容量
STE 内存中 存放完整的翻译配置 每个StreamID一个,数量可达数万
Context Bank SMMU内部 缓存最近使用的STE 通常几十到几百个

避坑指南:我曾经在一个项目里,把Context Bank的数量配置得太小,结果导致频繁的STE换入换出,性能直接腰斩。后来我根据实际设备数量,把Context Bank扩到了128个,问题就解决了。所以,Context Bank的数量一定要大于活跃的StreamID数量,这是基本法则。

3.4 TLB与微页表

TLB(Translation Lookaside Buffer)是SMMU里最关键的加速部件。它缓存的是页表项(PTE),而不是STE。说白了,TLB就是页表的硬件缓存。

当TLB Miss发生时,SMMU会启动微页表遍历。这是一个硬件状态机,它会按照ARMv8的页表格式,一级一级地去内存里查找。这个过程是纯硬件的,不需要软件介入,所以速度很快。

微页表遍历的层级取决于页面大小和地址空间配置:

  • 4KB页面:需要4级页表遍历(L0 -> L1 -> L2 -> L3)
  • 2MB页面:需要3级页表遍历(L0 -> L1 -> L2)
  • 1GB页面:需要2级页表遍历(L0 -> L1)

性能要点:TLB的命中率直接决定了IO性能。我个人建议,对于延迟敏感的设备(比如网卡、NVMe SSD),尽量使用大页(2MB或1GB),这样可以减少页表层级,提高TLB的覆盖范围。

你可能会问,TLB和Context Bank有什么区别?嗯,这个问题问得好。简单来说:

  • Context Bank:缓存的是“哪个设备用哪个页表”的映射关系(STE)。
  • TLB:缓存的是“虚拟地址到物理地址”的映射关系(PTE)。

两者是流水线上的不同环节。先查Context Bank找到页表基地址,再查TLB找到物理地址。如果TLB没命中,就启动微页表遍历去内存里查。

调试技巧:我在调优SMMU性能时,常用一个命令来查看TLB的统计信息:cat /sys/kernel/debug/arm-smmu/tlb_stats。如果看到Miss率超过10%,就该考虑优化策略了。要么调整StreamID的分配,要么改用大页。

好了,这一章的内容就到这里。SMMU的硬件架构,说白了就是一套为IO设备量身定做的地址翻译流水线。StreamID是入口,STE是配置,Context Bank是缓存,TLB是加速器,微页表是兜底方案。把这几个概念串起来,你就能看懂SMMU的Spec了。


公众号:蓝海资料掘金营,微信deep3321