IOMMU/SMMU:DMA 地址翻译、SVA 与设备隔离
更新时间:2026-09-19。前置:MMU/TLB 虚拟内存、AXI 总线、DMA 描述符环、总线防火墙。CPU 有 MMU 保护进程内存,但 PCIe/DMA 设备传统上直接发物理地址、能访问任意内存。IOMMU(x86 称 IOMMU/VT-d,ARM 称 SMMU)就是给总线主控设备用的 MMU。
一、为什么需要 IOMMU
没有 IOMMU 时,设备发起 DMA 事务直接带物理地址:
- 安全/隔离:被攻破或故障的设备可读写任意物理内存(包括内核、其他设备、密钥),总线防火墙只能做粗粒度区域过滤,无法按虚拟地址/进程隔离。
- 内存碎片化:DMA 要物理连续缓冲区(见 DMA 突发对齐),大块物理连续内存稀缺,要预留或 bounce buffer 拷贝。
- 虚拟化穿透:把设备直通给虚拟机(VFIO/passthrough)时,guest 给出的「物理地址」是 guest 物理地址,需要一层翻译到宿主物理地址并隔离各 VM。
IOMMU 串在设备与内存之间的读/写地址通道上,对每个 DMA 事务的地址做翻译和权限检查,类比 CPU MMU 但服务于 IO 事务。
二、基本结构与翻译过程
bash
设备发起 DMA(带 IO 虚拟地址 IOVA + 设备/流标识)
│ 总线(如 PCIe, AXI)
▼
IOMMU/SMMU
├─ 流表/上下文(Stream Table/CD): 由设备 ID(RequesterID/StreamID) 选上下文
├─ IO TLB(缓存翻译) miss → 走多级 IO 页表(类似 CPU 多级页表)
├─ 权限检查(R/W/priv/特权级),违例 → 总线错误/中断(block)
└─ 属性/内存类型转换
▼
翻译后物理地址 → 内存控制器
违例/故障 → event/中断/PRI 请求- 设备标识:PCIe 用 BDF(bus/device/function,Requester ID);ARM SMMU 用 StreamID;端侧可按 AXI ID/主设备号(与 总线编号、event router 的主控标识一致)。
- 多级 IO 页表:每个域(domain,通常一个 VM 或一个隔离的驱动上下文)一棵页表,把 IOVA 翻译成 PA,页大小 4K/2M/1G 等;支持与 CPU 相同或兼容的页表格式以便共享。
- IO TLB:缓存最近翻译,避免每个 DMA 事务都走多级表;和 CPU TLB 一样要处理失效(见 MMU TLB)。
- 权限/属性:可读/可写、特权、可执行概念、cacheable/设备内存属性;设备越权访问触发 fault(类似 CPU page fault)。
三、DMA 重映射(隔离/虚拟化)
最基础用途:把每个设备(或每组)绑定到一个域,只映射允许的缓冲。
- guest/驱动拿到的是 IOVA,IOMMU 映射到真正的宿主 PA;设备无法访问未映射区域,故障上报。
- 实现 设备之间、设备与内核之间的隔离;PCIe 设备直通虚拟机时,hypervisor 为每个 guest 建域,guest 以为自己控制物理内存,实际被 IOMMU 限制(安全启动/TrustZone 体系的设备侧补充,见 安全启动)。
- 把分散的物理页映射成一段连续 IOVA,设备看到连续地址而不必物理连续——消除大块连续 DMA 内存需求和 bounce buffer 拷贝(散射/聚集,见 DMA 描述符环)。
四、SVA:设备共享进程虚拟地址
Shared Virtual Addressing / SVM(ARM SVA):让设备和 CPU 上的进程共用同一套虚拟地址空间和页表。
- 用户程序可直接把栈/堆里普通指针(虚拟地址)交给设备做 DMA,设备用该 VA 发起事务,IOMMU 用该进程的页表翻译——不再需要驱动 pin 住物理内存、建 DMA 映射。
- 需要区分同一设备上来自不同进程/线程的事务:PCIe 用 PASID(Process Address Space ID)(TLP 带 PASID),SMMU 用 (StreamID, PASID) 选上下文;上下文描述符 ASID/VMID+VA 翻译。
- 需要 PRI(Page Request Interface)/ SMMU stall:设备访问的页可能被换出/未提交(页不在内存),设备可以发起缺页请求,IOMMU 通知 OS 把页调入后再让设备重试——使设备也能处理可分页内存。
- 依赖 ATS(Address Translation Services):设备端可缓存翻译(ATC,设备侧 TLB),先自己翻译、未命中再问 IOMMU(translation agent),减少 IOMMU 热点和延迟;配合失效(invalidation)保持一致。
- 典型用于加速器、支持 SVM 的 GPU/NIC/AI 设备统一编程。
五、一致性、属性与性能
- cache 一致性:IOMMU 翻译过程访问的页表本身要与 CPU 一致(硬件可走 coherent interconnect,见 CHI/ACE);翻译结果和 DMA 数据的 cacheable 属性要正确(见 DMA 与 cache)。
- TLB 失效:OS 改了页表(解除映射、进程退出)必须 invalidate 对应的 IO TLB 和设备 ATC(ATS invalidation),否则设备仍可能访问已释放内存(use-after-free/泄漏)。
- 故障处理:非法事务产生 event record/中断(地址、设备、类型),驱动复位设备或杀进程;PRI 缺页是「可恢复故障」,普通违例是「不可恢复/终止」。
- 性能:IOMMU 在每个事务地址路径上加查找延迟(TLB hit 影响小,miss 走多级表代价大);高吞吐设备要大 IO TLB、ATS、合理页大小(大页减少表级数)。
- 启动阶段:IOMMU 可能默认 bypass(不翻译)或全部阻断,固件/bootloader 要尽早建立初始映射(见 安全启动链)。
六、常见的坑
- 以为设备只能访问驱动给的缓冲:无 IOMMU 时设备可 DMA 任意物理内存,故障/被攻破设备无隔离。
- 页表/TLB 未失效:释放 DMA 缓冲后没 invalidate IO TLB/ATS,设备仍访问旧映射。
- 属性映射错:把设备内存当 cacheable 或反之,数据不一致/性能差(见 DMA cache)。
- 设备 ID/StreamID 配错:设备被分到错误域,隔离失效或 DMA 全 fault。
- 大流量下 IO TLB miss 风暴:小页+随机地址导致频繁走表,带宽下降;用大页/ATS。
- PRI 缺页处理不当:未处理页请求或死锁(设备等待、OS 又等待设备),要有超时和配额。
- PASID 混用:多进程共享设备时 PASID 未正确打标签,跨进程访问。
- ATS 缓存陈旧:设备 ATC 失效没做,翻译过期。
- 直通安全漏洞:设备可发任意 DMA(包括伪造 Requester ID 的恶意设备),需信任链和 ACS(访问控制服务)防止路径伪装。
- IOMMU bypass/阻断启动时序:上电默认策略与 bootloader 映射没衔接,早期 DMA 失败。
- 中断重映射遗漏:除了数据 DMA,MSI 中断写也应被重映射,否则可伪造中断。
- 页表内存一致性:非一致系统上 OS 更新页表但 IOMMU 读到旧缓存。
相关与延伸
CPU 侧原理见 MMU/TLB;粗粒度区域过滤对照见 总线防火墙;DMA 缓冲见 DMA 突发对齐、描述符环;一致互联见 CHI/ACE。
总结
IOMMU/SMMU 是挂在设备 DMA 路径上的 MMU:按设备的 RequesterID/StreamID 选择域上下文,用多级 IO 页表把设备发出的 IOVA 翻译成物理地址并做读写/特权/内存类型检查,命中 IO TLB 加速、违例产生 fault,从而既把碎片化物理页映射成设备可见的连续地址、又实现设备间和直通虚拟机之间的强隔离;进一步用 PASID 区分同一设备上不同进程、让设备共享进程页表(SVA/SVM),用 ATS 让设备缓存翻译、用 PRI 在页被换出时让设备发起可恢复缺页请求——最容易出错的是释放映射后不失效 IO TLB/ATS、设备 ID 配错域、内存/cacheable 属性转换错误和把中断/MSI 重映射遗漏。