news 2026/10/20 18:21:35

SMMUv3 硬件 PTW 详解:IOMMU 地址翻译、TLB 缓存与 DMA 异常排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SMMUv3 硬件 PTW 详解:IOMMU 地址翻译、TLB 缓存与 DMA 异常排查

今天想认真聊聊 SMMUv3 的硬件 PTW。以前我提到 SMMU 就说“IOMMU 嘛,给 DMA 做地址翻译”,直到在某块新平台的调试中,网络控制器 DMA 偶发访问异常,我才发现自己对“翻译到底是谁做的、在哪一层做、为什么改了页表还不起效”这些细节并没有真正吃透。这篇文章是那次排查之后的完整复盘。

SMMUv3 里的硬件 PTW(Page Table Walk,页表遍历)指的是:当一个 IO 事务需要地址翻译而翻译结果不在缓存里时,SMMU 不需要打断 CPU、不需要让驱动软件去一级一级读页表,而是由硬件自己把多级页表从头走到尾,最终得出物理地址。这套机制是 IOMMU 高性能的关键,也是虚拟化场景下 IO 设备隔离的基石。如果你在做设备驱动、IOMMU 子系统适配、虚拟化 DMA 透传,或者只是被一段 DMA 内存损坏问题折磨,这篇文章应该对你有用。

1. 从“软件查页表”到“硬件走查”:PTW 解决的真问题

1.1 没有 SMMU 时,DMA 是一盘散沙

在没有 SMMU 的系统里,外设发出的 DMA 地址就是物理地址。设备驱动说“我要访问 0x8000_0000”,DMA 就会直接读写那个物理位置。优点很简单:无翻译、无延迟。缺点也很致命:要么信任所有设备驱动,要么自己维护一份“这块内存不能被 DMA 碰”的地址排除名单。

虚拟化场景更麻烦。给客户机分配一个直通设备时,如果设备 DMA 能直接访问物理内存,客户机里一个写坏的描述符就可能改写宿主机内存。设备直通要安全,就必须让客户机里的驱动操作的是“看起来属于自己”的地址,而不是真正的物理地址。

所以 SMMU 的核心工作,是把 DMA 地址变成 IO 虚拟地址(IOVA)。设备发出的地址不再直接落到内存,SMMU 负责把 IOVA 翻译成系统物理地址。翻译需要查页表,而“谁去查页表”就是一个绕不开的架构选择。

1.2 软件参与页表遍历,代价比想象中大

早期或者简化实现里,翻译缺失经常要走软件路径:TLB 未命中后,通过中断或命令队列通知软件,软件去内存里逐级读页表,算出物理地址,再写回 TLB。听上去每笔 DMA 只需要一次额外处理,但现代高吞吐网卡有几十个队列,每个队列每秒可以产生上百万个 DMA 请求。如果每次 miss 都要触发一次软件 walk,CPU 开销会瞬间拉满,DMA 延迟也会剧烈抖动。

软件遍历还有一个工程问题:页表在内存里,软件要读取它,就得保证自己运行在正确的地址空间、正确的页表层级、正确的权限下,而这些代码本身还可能被调度、被中断打断。一次遍历往往要串行读好几个内存地址,任何一个环节慢,整条设备 I/O 路径就慢下来。硬件 PTW 把这件事收进片上状态机,目的就是去掉这条串行软件链路。

1.3 硬件 PTW 带来的三个直接收益

  • 第一是延迟可控。翻译缺失时硬件自动从根表逐级读内存,无需 CPU 参与,大多数情况就是一次或两次内存读,加上一次 TLB 填充。
  • 第二是并发。越来越多的 SMMU 实现会配备不止一个 PTW 引擎,不同 Stream、甚至多个队列的 DMA 请求可以并行 walk,互不阻塞。软件遍历做不到这种并发度。
  • 第三是兼容标准页表软件栈。SMMUv3 的 PTW 直接吃标准的 LPAE 多级页表格式,内核 IOMMU 子系统里io-pgtable框架支持这种格式,驱动侧创建和销毁页表的逻辑可以完全复用。

其实还有第四点:硬件 PTW 在 walk 过程中可以做中间级缓存和预取,这一点放到第三章细说。理解这点,才算真正理解硬件 PTW 的性能优势。

2. 一次 IO 事务从提交到命中:STE、CD 和多级描述符的实际路径

2.1 设备怎么找到自己的页表

一个 IO 事务进入 SMMU 后,SMMU 先拿事务携带的 StreamID 去查 Stream Table。STE(Stream Table Entry)是第一步的关键。STE 允许三种策略:bypass、abort、translate。如果配置成 translate,SMMU 才会真的开始页表遍历。

具体从哪张页表开始,取决于是否开启两级翻译。只使用 stage2 时,STE 里直接放 S2TTB(stage2 页表基地址)和 VMID;需要 stage1 时,STE 里的配置会指向一份 CD(Context Descriptor),CD 里再放 TTB0/TTB1、ASID、地址宽度等字段。支持 SubstreamID 时,一个 Stream 下还能再细分多个 substream,和 PCIe 的 PASID 概念对应。

所以“硬件 PTW 的根”不在某个固定寄存器里,而在 STE/CD 这套间接配置里。这也是排查时最容易被忽略的地方:你以为页表挂上了,实际上 SMMU 连根表地址都取错了。

2.2 多级页表的遍历规则

以 4KB granule、48 位输入地址为例,硬件把虚拟地址切成 9+9+9+9+12 五个部分,前四级分别是 Level0 到 Level3 的索引,最后 12 位是页内偏移。PTW 先从根表取 Level0 描述符,再根据描述符类型判断:

  • 如果描述符是 table 类型,继续用下一段地址索引去取 Level1 描述符;
  • 如果是 block 类型,说明这一大块地址直接映射到一段连续物理内存,也就是大页映射;
  • 到了 Level3,看到 page 描述符,翻译完成。

每个描述符里除了输出地址,还带着权限、缓存属性、访问标志位等。硬件 PTW 会逐级检查,属性不合法就立刻终止并产生事件。

这个过程和 CPU 侧 MMU 的页表遍历几乎一模一样,把多次内存访问变成了硬件内部操作。区别在于,CPU 侧的硬件 walk 是为单核多线程服务的,SMMU 侧的 walk 则服务于多个设备,缺页时可能有多个请求在等同一个描述符,因此硬件里通常还有对同一缓存行的合并逻辑,避免重复读内存。

2.3 一次翻译究竟要读几次内存

很多人以为一次翻译就是读“页表”一次,实际未必。用一个刚建立的映射做例子:

  • 如果 TLB 没有命中、中间级也没有缓存,48 位地址从 Level0 走到 Level3,理论上要读 4 次内存;
  • 如果中间某一级命中了 walk cache,通常能省掉若干次;
  • 如果映射用了 2MB 大页,Level0 之后直接得到 block 描述符,只需要读更少的次数。

这个“读几次内存”的差异直接决定了 DMA 首次访问的延迟。某些对延迟敏感的存储设备,头几个 I/O 的时延偏差就是从这来的,也是后面讲调优时为什么要优先用大页的原因。

2.4 CMDQ:软件和硬件 PTW 之间的调度界面

这里要强调一个容易混淆的点:软件并不会“启动”一次 PTW。设备 DMA 请求到达 SMMU、翻译不在缓存时,PTW 是自己开始的。软件和硬件的真正交互界面是命令队列(CMDQ)。

CMDQ 里最常见的是 TLBI 和 SYNC。TLBI 的作用是告诉 SMMU:某段范围内的缓存失效,下次访问不要再用旧结果;SYNC 则是让队列里之前的命令必须执行完毕后再继续。所以软件能控制的不是“怎么翻译”,而是“什么时候让缓存里的旧结果失效”。这个语义理解到位,很多奇怪的 DMA 问题都能定位了。

3. TLB 与中间缓存:页表更新失效的脏数据陷阱

3.1 TLB 里存的是什么

TLB 缓存的是完整翻译结果:一个 IOVA 对应哪个物理地址、什么权限、什么属性。设备访问是高频的,TLB 命中时不需要再做任何页表遍历,直接拿结果去访问内存。

要注意 TLB entry 通常还带 ASID/VMID 标签,用来区分不同地址空间。stage1 用 ASID、stage2 用 VMID,缓存里同一个虚拟地址的翻译结果可能同时存在,互不串扰。

问题恰恰在这里:软件改了页表,如果 TLB 里还留着旧映射,那么设备下一次访问依然会被翻译到旧物理地址。这跟 CPU 侧改页表不刷 TLB 是一个道理。差别在于 CPU 刷 TLB 是一条特权指令,而 SMMU 刷 TLB 要走 CMDQ 下发;CPU 是自己主动失效自己的缓存,SMMU 则是软件发命令让另一个硬件单元去失效,时序上要更小心。

3.2 walk cache:比 TLB 更容易忽视的一层

有些实现除了 TLB,还把 PTW 过程中读到的中间级描述符放在专门的 walk cache 里。比如 Level1 的表项、Level2 的表项,单独缓存。

这意味着什么?即使你把 TLB 失效了,但 walk cache 里残留的中间描述符仍可能被 PTW 使用,然后得到一条错误的完整路径。要命的是,如果中间级页表被复用或释放,walk cache 里留着旧地址,下一次 walk 可能读到一个已经被挪作他用的内存区域,轻则翻译错误,重则读到非法描述符产生 fault。

所以正确理解是:只要页表内容变化了,就必须做相应范围的 TLBI;TLBI 不仅负责 TLB,也会驱动硬件把相关中间缓存一并作废。不要因为“我只改了二级页表,TLB 里又没存这一层”就不发命令。

3.3 TLBI 命令的正确顺序

一个安全顺序应该是:

  1. 在内存中更新页表描述符;
  2. 通过内存屏障确保页表更新的可见性;
  3. 通过 CMDQ 下发对应范围的 TLBI;
  4. 下发 SYNC,确认失效完成;
  5. 之后才允许释放或复用被替换的页表内存。

第 5 步最容易被省略。不少人 unmap 之后忘了 TLBI+SYNC,于是某天设备偶发 TRANSLATION fault。等排查到的时候,页表内存可能已经被其他驱动覆盖,连 fault 本身的地址都难以解释。

再补一个批量技巧:如果一次要解除几百个映射,不必每个 unmap 都发一条 TLBI。可以一边批量更新页表,一边把需要失效的 VA 范围记下来,然后一次性下发多条 TLBI,最后统一 SYNC。这样 CMDQ 不会被频繁撑满,性能上明显更稳。

3.4 缓存一致性与“写页表”的隐性要求

还有一层是内存属性。PTW 读页表本质是一次系统互联上的内存读。如果页表所在内存是 normal cacheable 并已被 CPU 写入缓存,而 SMMU 与 CPU 之间没有或只有部分缓存一致性支持,那么 PTW 可能读到旧内容。

很多现代 SoC 上 SMMU 与 CPU 是 coherenct 的,页表写在 cache 里也能被 PTW 看到。但不代表所有平台都会保证这一点。遇到“页表明明改了,SMMU 还是老样子”的时候,不要只怀疑 TLBI,先看页表更新有没有做 clean/write back,或者干脆把页表放 non-cacheable 区域做对照实验。这个坑我在某个新平台的调试中确实碰到过,当时浪费了不少时间。

4. Stage1/Stage2 与安全属性:硬件 PTW 如何守住隔离边界

4.1 Stage1/Stage2 真的会查两遍吗

虚拟化场景下,客户机和虚拟化层各有一张页表。客户机管 stage1:设备看到的 IOVA 转 IPA;虚拟化层管 stage2:IPA 转最终 PA。

SMMUv3 开启 nested 翻译时,硬件 PTW 先按 stage1 走一遍,得出 IPA,再用 IPA 作为输入按 stage2 走第二遍,得出 PA。这个过程完全由硬件完成,对客户机和虚拟化层都是透明的。代价是翻译路径变长,如果每级都不命中缓存,stage1 加 stage2 最多会读很多次内存。所以裸金属场景或者不需要虚拟化的场景,通常会关闭 stage1,直接让 STE 里配 stage2 甚至 bypass,换取更低的 DMA 时延。这个取舍要在性能和隔离之间自己权衡。

4.2 Secure/Non-Secure 是 PTW 的一条硬边界

SMMU 要防护的不只是“地址对不对”,还有“谁有资格读哪块内存”。ARM 体系里有 Secure/Non-Secure 的世界划分;Non-Secure 世界运行的组件,不应该有能力让 DMA 直接读到 Secure 世界的内存。

STE 里记录了 stream 对应的安全状态。硬件 PTW 在遍历页表时,也会根据 stream 的安全属性决定能访问哪段地址空间。如果配置把一个 Non-Secure stream 的 STE 指向了 Secure 内存里的页表,硬件不会“尝试之后失败”,而是会更早地在配置层面判定不合法,阻止翻译发生。这其实是把 DMA 变成一种“带权限的内存访问”的关键机制。

实际产品里,安全侧固件通常会把 SMMU 的某些 stream 配置成 abort 或限制在明确的安全地址范围。调试时如果看到设备怎么都拿不到地址,先检查 stream 的安全属性和 STE 配置是否匹配,优先于去分析页表内容。

4.3 权限位不匹配时,硬件去哪报告

页表描述符里除了地址,还有读写许可、访问标志等。一个设备对 read-only 页发起写 DMA,或者对不存在映射的地址发起访问,PTW 都会在相应阶段停止,并产生一个 event。SMMUv3 的事件队列里会记录类型、StreamID、SubstreamID、地址等信息。

常见的事件类型包括:地址翻译失败、权限失败、地址宽度超出限制、页表格式非法等。每种类型的排查方向完全不同:

  • 翻译失败,先怀疑页表有没有建好、STE/CD 是否生效;
  • 权限失败,先看描述符的 AP 位和事务的读写方向;
  • 地址宽度失败,往往和设备自身寻址能力、输入地址宽度配置有关。

拿到 event record 时,不要只看“SMMU 报错了”,把 type、地址、StreamID 三个字段同时拿出来,往往能直接定位到驱动里的某一行配置。

5. 实际排错与调优:我在项目中遇到的 PTW 问题

5.1 我建议的排错第一站:Event Queue

遇到 DMA 异常,我的习惯是先读 Event Queue,而不是先翻驱动代码。原因是 event record 是硬件的直接现场。

要看的信息有这么几项:StreamID(哪个设备)、event type(哪类失败)、翻译阶段(stage1 还是 stage2)、出错的地址。用这些信息回查设备树或驱动的 stream 配置,很快能确定方向。

一个常见假象:设备已经被复位或者驱动已经被卸载,但 Event Queue 里还堆着之前的 fault。所以排错时要先确认记录的时间或单调递增计数,别拿死亡现场当现行犯。

5.2 用 SMMU 的 PMU 定位性能问题

功能通了之后就是性能。SMMUv3 有 PMCG(Performance Monitor Counter Group),可以统计 TLB 命中/未命中、PTW 次数等。如果你发现某个设备吞吐上不去,又不能解释为 CPU 瓶颈,建议先看几个计数:

  • TLB miss 率是否异常高;
  • PTW 次数是否和设备 I/O 频率成正比;
  • 大页映射是否能明显降低 miss 率。

调优动作通常是两个方向:一个是把 IOVA 空间尽量做成连续大块,让映射命中 block 描述符;另一个是减少 map/unmap 频率,让热路径上的 TLB 长期命中。我见过某些驱动为了省内存,每笔 DMA 都用小块映射,结果 TLB miss 率超过一半,改成预分配环形区域后性能直接翻倍。

5.3 让我花时间最长的几个坑

这里挑几个我在实际调试中真正踩过、或者陪别人查了很久的典型问题:

现象根因处理
偶发 TRANSLATION faultunmap 后没有做 TLBI+SYNC 就释放页表内存先补齐失效命令,再释放内存
DMA 访问到了错误物理地址STE 被配置成 bypass,页表形同虚设检查 STE 的 translate 策略
DMA 性能突然暴跌stage1+stage2 双重 walk,且页表全是 4KB用 2MB 大页或关闭不必要的 stage1
修改页表后长时间不生效walk cache 残留,或页表写后未做 clean发对应 TLBI,并检查页表内存属性

每个坑单拎出来都不难,难的是组合场景。比如“偶发 TRANSLATION fault”再加上“设备刚好在卸载时触发”,如果不了解 TLBI 生命周期,很容易误判成硬件不稳定。

5.4 一个可以直接拿去用的检查顺序

我的建议是配置阶段、运行阶段、异常阶段分开查。

配置阶段:

  1. 确认 STE 基址、CD 基址写入正确,且按硬件要求的对齐方式配置;
  2. 确认 STE 的 translate/bypass/abort 策略符合预期;
  3. 确认 CD 和 STE 里的地址宽度、ASID/VMID 没有冲突。

运行阶段:

  1. 验证页表内存可见性,必要时做 clean/write back;
  2. 大量更新页表时,使用批量 TLBI 加单次 SYNC;
  3. 用 PMCG 观察 TLB miss 曲线,及时优化映射块大小。

异常阶段:

  1. 第一时间读 Event Queue,记录 StreamID、type、地址;
  2. 反查 STE/CD 配置,确认是不是配置层问题;
  3. 确认没有未失效的 TLB/walk cache,再执行内存释放。

这套顺序看起来朴素,但能把大多数 SMMUv3 问题压缩到一到两个排查轮次。别上来就怀疑 SMMU 硬件坏了,绝大多数问题都出在页表和命令队列的时序上。

这篇文章写到这,其实我最想表达的一点是:SMMUv3 的硬件 PTW 不是一个简单的“硬件帮你查表”开关,它是把 CPU 侧 MMU 的各种局部性设计搬到了 IO 侧,并且把页表一致性的责任明确交到了软件手里。最初我配完 STE 就以为完事了,后来经历的几次问题都跟页表生命周期的顺序有关。有一次恰好把 TLBI 放在 DMA 完成回调之后执行,排了很久才发现是顺序错位,页表已经释放,PTW 还在走查。如果你也在调类似问题,不妨先按上面的顺序把 Event Queue、TLBI、walk cache 这条线索理一遍。硬件 PTW 也许不会让你眼前一亮,但把这块机制吃透之后,处理 DMA 相关的怪问题会从容很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 18:21:35

海康威视ISAPI对接实战:从Digest认证到报警订阅全流程避坑指南

简介:海康威视ISAPI协议官方文档,面向安防设备集成开发人员与平台对接工程师,用于解决摄像机、网络录像机等设备与客户端或平台之间的接口对接问题。文档基于HTTP与REST架构,系统阐述ISAPI协议体系,内容涵盖设备管理、…

作者头像 李华
网站建设 2026/10/20 18:21:26

DeepSeek Agent框架插件化设计拆解:从注册到执行的全链路解析

1. 从一次插件改造需求说起:为什么要拆这个框架前段时间接手了一个内部工具链的整合任务,核心诉求说起来很简单:把几个散落在不同项目里的自动化能力——代码审查、日志分析、接口巡检——统一到一个对话式入口里,让团队成员用自然…

作者头像 李华
网站建设 2026/10/20 18:20:59

HuggingFace图像-文本数据集构建全流程:从本地图片到Hub流式加载

搞多模态训练或者给图文检索、图像生成做数据准备的时候,第一步往往不是写模型,而是先把图像-文本数据集理顺。接触 HuggingFace 生态的同学应该深有体会:datasets库加载数据集非常方便,但很多人到了“我想把自己手里的图片和 cap…

作者头像 李华
网站建设 2026/10/20 18:20:39

Tushare最新源代码解析:从接口调用到二次开发实战指南

简介:tushare这一开源财经数据接口库,其最新源代码包适合金融数据分析师、量化策略研究者以及有进阶需求的开发人员。研读源码可理解网络请求的封装、行情数据的解析、异常情况的处理,以及多线程和异步输入输出在性能优化上的运用。资源囊括四…

作者头像 李华
网站建设 2026/10/20 18:19:43

AI视频创作三件套:Deepseek+即梦AI+Midjourney高效协作指南

做自媒体,最卡人的从来不是创意,而是"想法到成片"之间那条又长又碎的流水线。文案要写、画面要拍、配音要配、封面要设计,一个人恨不得拆成八个人用。我过去一年大部分时间都耗在这种"一个人活成一支队伍"的状态里&#…

作者头像 李华
网站建设 2026/10/20 18:18:42

程序员创意开发大赛全攻略:从WorkBuddy平台到麦当劳奖品

打开赛事主页的那一刻,我第一眼看到的不是“创意开发大赛”几个大字,而是奖品列表里的“10240积分”。这个数字太会了。程序员一眼就能看穿:10240 1024 10,而1024是程序员最敏感的编码,2的10次方,也是每年…

作者头像 李华