SMMU 架构概览:系统 MMU 在 SoC 中的位置与核心功能

各位同学,咱们今天正式开讲 SMMU。说实话,我做了十几年芯片底层验证,接触过不少系统级的设计,但 SMMU 这块儿,真的是「牵一发而动全身」。你想想看,一个 SoC 里那么多加速器、DMA 控制器、网络接口,它们都要访问内存,如果没有一个统一的地址翻译和保护机制,那系统早就乱套了。

我个人习惯把 SMMU 理解成「系统级的内存保镖」。它不直接管 CPU 的地址翻译——那是 CPU MMU 的活——但它管的是所有其他「非 CPU 主设备」的地址翻译。说白了,就是让 GPU、NPU、存储控制器这些家伙,也能享受到虚拟地址带来的便利和安全隔离。

1.1 SMMU 在 SoC 中的位置

我们先看一张图,这是我手绘的 SMMU 在 SoC 中的典型位置。你注意看,它横跨在系统总线和内存控制器之间,所有非 CPU 主设备的访问请求,都要经过它。

核心要点:SMMU 是 SoC 中「非 CPU 主设备」的地址翻译与访问控制中心。它位于系统总线(如 AXI/ACE/CHI)与内存控制器之间,对下游设备透明。

SMMU 在 SoC 中的位置与数据流 主设备集群 GPU NPU / AI 加速器 DMA 控制器 存储控制器 网络接口 显示控制器 加密引擎 ... 其他加速器 SMMU 地址翻译 权限检查 TLB 缓存 多级页表遍历 事务属性转换 安全隔离 异常处理 内存子系统 DDR 控制器 片上 SRAM IOMMU 映射表 页表缓存 配置空间 中断控制器 事务请求 物理地址访问 翻译结果/错误 页表遍历 说明:SMMU 对所有非 CPU 主设备的事务进行拦截、翻译和权限检查,然后以物理地址访问内存子系统。 页表遍历(walk)会回读内存中的页表结构,这是 TLB 缺失时的主要性能瓶颈。

从这张图你能看到,SMMU 就像是一个「交通警察」。所有主设备发出的访问请求,先到 SMMU 这里报到。SMMU 查一下自己的 TLB,如果命中,直接翻译成物理地址放行;如果没命中,就得去内存里翻页表——这就是我们后面要重点讲的 TLB 缺失处理。

我的经验:在实际项目中,SMMU 的位置选择非常关键。我记得有一次,一个 SoC 把 SMMU 放在了离内存控制器很远的位置,结果因为走线延迟,导致 TLB 缺失时的页表遍历时间增加了 30%。后来我们不得不重新调整布局。所以,SMMU 的物理位置和总线拓扑,直接决定了系统的性能上限。

1.2 SMMU 的主要功能模块

SMMU 不是一块简单的硬件,它内部有好几个协同工作的模块。我把它拆解成五个核心部分,你对照着上面的图看,会更清楚。

功能模块 职责描述 我踩过的坑
TLB(翻译后备缓冲器) 缓存最近使用的地址翻译结果,加速翻译过程 TLB 条目数不够,导致频繁缺失,性能断崖式下跌
页表遍历器(Page Table Walker) TLB 缺失时,自动遍历内存中的多级页表,获取翻译结果 页表遍历深度没配好,一次缺失要读 4-5 次内存
配置与控制逻辑 处理 Stream ID、Substream ID,管理上下文描述符 Stream ID 配置错误,导致设备访问了错误的地址空间
异常处理单元 处理翻译错误、权限错误,上报中断或异常 异常处理路径没验证,死锁后系统完全卡死
事务属性转换 修改事务的缓存属性、安全属性、QoS 优先级等 属性转换没做对,DMA 写的数据 CPU 一直读不到

这五个模块里,我最想强调的就是 TLB 和页表遍历器。为什么?因为这两个模块直接决定了 SMMU 的性能。你想想看,一个高速的 DMA 传输,如果每发一笔请求都要等 TLB 缺失处理,那带宽就全浪费在等待上了。

注意:SMMU 的 TLB 和 CPU 的 TLB 有一个本质区别——SMMU 需要处理多个不同设备的地址空间。每个设备有自己的 Stream ID,甚至还有 Substream ID。这意味着 SMMU 的 TLB 必须支持「上下文切换」,而且切换成本比 CPU 高得多。我在一个项目中就遇到过,因为频繁切换上下文,TLB 命中率从 95% 掉到了 60%,性能直接腰斩。

1.3 SMMU 与 CPU MMU 的异同

很多刚接触 SMMU 的同学会问:「这不就是 CPU 的 MMU 吗?有什么好学的?」嗯,这个问题问得好。我刚开始也这么想,直到被现实狠狠教育了一顿。咱们来对比一下。

对比维度 CPU MMU SMMU(系统 MMU)
服务对象 单个 CPU 核心 多个非 CPU 主设备(GPU、DMA、NPU 等)
地址空间数量 通常 1-2 个(用户态/内核态) 成百上千个(每个设备可以有独立的地址空间)
TLB 结构 多级 TLB(L1/L2),专为单线程优化 通常单级或两级,需要支持多上下文
页表格式 与架构绑定(x86、ARM、RISC-V 各不相同) 通常与 CPU MMU 兼容,但可独立配置
异常处理 触发 CPU 异常,由操作系统处理 触发系统中断或返回错误响应给主设备
性能敏感度 对延迟极度敏感(影响指令执行) 对带宽更敏感(影响数据吞吐量)
缓存一致性 通常参与硬件一致性协议 可选支持,但实现复杂

你看,虽然都叫 MMU,但 SMMU 的复杂度要高得多。CPU MMU 只需要管好自己的一亩三分地,而 SMMU 要同时伺候几十上百个「大爷」。每个设备都有自己的脾气——有的要低延迟,有的要高带宽,有的要安全隔离。

我记得有一次调试一个 GPU 相关的性能问题。GPU 通过 SMMU 访问内存,但帧率一直上不去。我查了半天,发现是 SMMU 的 TLB 配置太小,而且没有开启预取功能。GPU 的访问模式是流式的,每次访问新地址都要触发 TLB 缺失。后来我调整了 TLB 大小,并开启了硬件预取,帧率直接提升了 40%。

核心差异总结:

  • CPU MMU = 专车司机,只服务一个老板,路线固定,反应快
  • SMMU = 出租车调度中心,同时服务多个乘客,路线复杂,需要调度策略
  • 两者共享页表格式和翻译机制,但 SMMU 需要额外的上下文管理和多设备隔离能力

1.4 为什么 SMMU 验证这么难?

说到这儿,我想聊聊验证。SMMU 的验证为什么是芯片验证里的「硬骨头」?我总结了三个原因。

  1. 并发场景多:多个设备同时发起访问,SMMU 要同时处理多个翻译请求。TLB 的锁机制、仲裁逻辑、一致性维护,都是容易出 bug 的地方。
  2. 状态空间爆炸:每个设备有自己的 Stream ID、地址空间、权限配置。组合起来的状态空间,用穷举法根本测不完。
  3. 性能边界难捉:功能正确不代表性能正确。TLB 缺失率、页表遍历延迟、上下文切换开销,这些性能指标在验证阶段很难覆盖全。

我曾经在一个项目中,SMMU 的 RTL 代码在仿真环境里跑了三个月,功能测试全过。结果芯片回来后,在特定压力测试下,SMMU 的 TLB 出现了死锁——两个设备同时访问同一个 TLB 条目,锁没释放干净。这个 bug 在仿真里从来没复现过,因为仿真速度太慢,触发不了那种精确的时序竞争。

我的建议:做 SMMU 验证,一定要重视「随机压力测试」和「边界时序测试」。不要只盯着功能正确性,要多想想「如果两个设备同时发请求会怎样?」、「如果 TLB 满了又来了新请求会怎样?」、「如果页表遍历过程中内存响应延迟了会怎样?」。这些场景才是 bug 的高发区。

1.5 本章小结

好了,咱们第一章的内容就到这里。你只需要记住三件事:

  • SMMU 是 SoC 中所有非 CPU 主设备的地址翻译和访问控制中心
  • 它的核心模块包括 TLB、页表遍历器、配置控制、异常处理和事务属性转换
  • 与 CPU MMU 相比,SMMU 需要处理多设备、多上下文的复杂场景,验证难度更大