ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

IOMMU 开启取舍:DMA 隔离、设备直通与故障排查

IOMMU 开启取舍:DMA 隔离、设备直通与故障排查 1. 从一块乱写内存的扩展卡说起IOMMU 到底挡的是什么几年前遇到过一个让我印象很深的问题一台跑着几台虚拟机的宿主插了一块二手的万兆网卡前两周一切正常第三周开始出现随机的进程崩溃、压缩包解压报 CRC 错误、数据库偶发校验失败。换内存、换系统盘、跑 memtest 全部通过折腾了整整两天最后是在内核日志里翻到一行以前从没认真看过的报错才把方向从内存坏了转到DMA 越界上。那行日志来自IOMMUInput/Output Memory Management Unit输入输出内存管理单元。它替我们抓到了那个凶手网卡固件在处理某个边界长度时描述符算错了一位往一块不属于它的物理内存里写了数据。如果这台机器的 IOMMU 没开这个错误会一直静默地破坏内存直到某天数据彻底对不上而你会永远怀疑是内存条的问题。所以这篇文章想聊的不是IOMMU 是什么这种教科书定义而是一个更实际的问题在什么情况下开启 IOMMU 是必须的在什么情况下开了反而给自己找麻烦以及开与不开之间你到底交换了什么。无论你是折腾虚拟化直通的玩家还是维护几台物理服务器的运维或者只是给家用 NAS 加块扩展卡这套判断逻辑都用得上。1.1 没有 IOMMU 时设备的 DMA 是一张空白支票要理解 IOMMU 的价值得先知道没有它的时候世界有多裸奔。CPU 访问内存要经过 MMU 做虚拟地址到物理地址的翻译这一步有页表、有权限位、有用户态和内核态的隔离写错了立刻触发缺页异常程序被杀掉。但设备发起的 DMA 完全不走这条路。一块支持总线主控Bus Master的网卡、阵列卡、NVMe 盘它们的 DMA 引擎拿到的是驱动填进描述符里的地址。在传统模式下这个地址就是物理地址——设备说我要把这块数据写到 0x7f3a0000内存控制器就老老实实照办没有任何中间人过问你是哪个设备这块内存归不归你你是读还是写。这意味着什么意味着一个固件有 bug 的网卡、一块山寨的扩展卡、一个描述符环算错位的驱动就能往任意物理地址上覆盖数据。而且这种破坏有三个非常讨厌的特征随机性取决于当时的物理内存布局、延迟性可能写坏的是几小时后才被读取的缓存、误导性报错的地方和出事的地方完全无关。你看到的可能是某个进程段错误真正的问题源头却是另一块卡。提示很多玄学不稳定的机器最后查出来都是 DMA 越界或者地址位宽不匹配。判断方法很简单——看内核日志里有没有 IOMMU fault 或者 DMA 相关的告警。当然前提是你得先把 IOMMU 打开它才有能力告警。1.2 IOMMU 的三层职责翻译、检查、隔离IOMMU 的结构和 CPU 的 MMU 很像只是服务的对象从进程换成了设备。它挂在内存控制器和 I/O 总线之间所有设备的 DMA 请求都要先过它这一关。它干三件事第一件是地址翻译。设备驱动不再往描述符里填物理地址而是填一个 IOVAI/O Virtual Address。设备拿着 IOVA 发起请求IOMMU 查自己的页表翻译成真实物理地址。这一层和 CPU 页表几乎同构也支持多级页表和大页。第二件是权限检查。每个页表项上都有读写权限位。设备只有读权限的区域写请求会被直接拦下并产生 fault。这就把设备能干什么从物理上能到达哪里收窄成了驱动明确授权的那几页。第三件是隔离。IOMMU 把设备划分成不同的 domain每个 domain 一套独立页表。A 设备的页表里根本没有 B 设备缓冲区的映射所以 A 就算疯了一样乱发请求也碰不到 B 的数据。这一条是虚拟化设备直通的基础后面会展开讲。打个生活化的比方没有 IOMMU 的时候快递员知道你家的门牌号可以自己上门、自己开门、自己翻你抽屉。有了 IOMMU你只给他一个快递柜编号他只能把包裹放进那个柜子柜子以外的区域他既进不去也不知道在哪。柜号和门牌号的对应关系只有小区的管理处IOMMU知道。1.3 开了和没开实际差别在哪把差别摊开成一张表会更直观维度未开启 IOMMU开启 IOMMU设备看到的地址物理地址IOVA虚拟地址DMA 越界后果静默破坏任意内存被拦截并产生 fault 日志设备间隔离无互相可访问按 domain 隔离设备直通虚拟机无法安全实现可以配合 VFIO外部接口 DMA 防护基本没有可在固件配合下启用DMA 映射开销接近零建立/销毁映射有成本故障可观测性几乎为零fault 日志可定位到具体设备部分老设备兼容性无影响可能出现映射失败或走 bounce buffer这张表里最容易被低估的是倒数第二行——可观测性。开了 IOMMU 之后硬件层面的越界访问会变成一条明确的日志带上设备的 BDF总线:设备:功能号、出错地址、访问类型和原因码。这对排查疑难杂症的价值有时候比隔离本身还大。而最容易被高估的是最后一行。很多人担心开了 IOMMU 性能掉一半这个说法在十几年前有一定道理现在的硬件代次和内核实现下绝大多数场景的性能影响在个位数百分比以内。真正会掉性能的是另一件事设备 DMA 能力受限时被迫走 bounce buffer这个后面单独讲。2. 设备直通场景为什么把硬件交给虚拟机就绕不开 IOMMU如果说普通用户开 IOMMU 是顺手加个保险那对做虚拟化直通的人来说IOMMU 就是硬性前置条件没有它整个方案根本不成立。这一节把直通这条路走一遍顺便解释为什么有些卡能直通、有些卡怎么折腾都不行。2.1 设备直通的最小必要条件在 Linux 上做设备直通主流路径是 VFIO 框架把目标设备从原驱动上解绑绑到vfio-pci上然后把设备对应的 IOMMU group 整个交给虚拟机。虚拟机里的驱动以为自己在一块真实的物理设备上工作实际上它的所有 DMA 请求都被 IOMMU 翻译到了虚拟机自己的内存区域里。这里的关键在于整个 group这四个字。IOMMU 的隔离粒度不是单个设备而是 group。同一个 group 内的设备共用一套地址翻译结构IOMMU 没有办法区分这个请求是 group 里 A 设备发的还是 B 设备发的。所以你要把 group 里的某一个设备给虚拟机就必须把整个 group 都给出去。很多人第一次做直通失败报的就是这个错# 直接把单块设备交给虚拟机时常见报错 vfio-pci 0000:03:00.0: not a valid IOMMU group member # 或者 qemu-system-x86_64: -device vfio-pci,host03:00.0: vfio 0000:03:00.0: failed to add group意思很直白这块设备所在的 group 里还有别的成员你得一起处理。2.2 IOMMU Group 是怎么划分出来的group 的划分依据来自固件提供给内核的 DMA 重映射报告表Intel 平台叫 DMARAMD 平台叫 IVRS。内核解析这张表结合 PCIe 拓扑把设备分组。分组的基本原则是如果两个设备之间的 DMA 请求无法被 IOMMU 区分来源它们就在同一个 group。什么情况会导致无法区分典型的是它们挂在同一个 PCIe 桥下面而这个桥又不支持 ACSAccess Control Services。ACS 是 PCIe 的一个可选特性作用是让下游端口的请求带上来源标识从而让 IOMMU 能分辨这个请求是从哪个下游口来的。支持 ACS 的交换机和根端口能让每个下游设备独立成组不支持的话整条链路下面的设备就打包在一起了。所以你会发现一个很有意思的现象同一块卡插在不同插槽上直通结果可能完全不同。插在 CPU 直连的 PCIe 槽上往往能独占一个 group插在通过 PCH 扩展出来的槽上可能和旁边的网卡、USB 控制器挤在一个 group 里怎么调都不行。2.3 分组不理想时的三条路以及各自的代价分组不理想的时候实际能走的路就三条每条都有代价第一条是换插槽。成本最低、风险最小优先试。物理机箱打开把卡从 PCH 扩展槽挪到 CPU 直连槽再跑一遍分组查询脚本看结果。很多时候这一步就解决了。第二条是用 ACS 覆盖补丁。社区里有给内核打的pcie_acs_override补丁可以让内核忽略固件报告的拓扑限制强制把设备拆成独立组。命令写法大致是# 内核启动参数需要打了对应补丁的内核 pcie_acs_overridedownstream,multifunction这条路要特别谨慎。它做的事本质上是绕过硬件层面的隔离能力让 IOMMU 以为设备是隔开的实际上硬件上并没有。如果 group 里的另一个设备被分配给了别的地方两者的 DMA 是能互相看见的。自己做实验无所谓放到多租户或者生产环境里就是安全隐患。我的态度是能不覆盖就不覆盖实在要覆盖只给这台机器自己用不接入任何不可信的工作负载。第三条是放弃直通改用 virtio 或者 SR-IOV。virtio 走的是软件模拟路径性能不如直通但兼容性极好SR-IOV 需要硬件支持虚拟功能一块物理卡虚拟出多个 VF每个 VF 天然独立成组是数据中心里更干净的方案。代价是网卡、阵列卡要选支持 SR-IOV 的型号价格通常高一档。方案隔离强度性能兼容性适用场景整组直通高接近原生受分组限制单卡独占、独立插槽ACS 覆盖后直通中低接近原生好单机实验、受控环境virtio 半虚拟化高软件层中等极好通用虚拟化、网络吞吐一般SR-IOV高硬件隔离高需硬件支持多虚拟机共享一块物理卡这张表的重点不是让你选性能最高的那个而是提醒你隔离强度和性能是可以分开权衡的两个维度。很多人只盯着性能跑分忽略了隔离被削弱带来的连带风险。3. Intel、AMD、ARM 三个平台的开启方式差异确认了为什么要开接下来是怎么开。这部分看似只是抄几行参数实际上不同平台的坑点差别挺大尤其是固件层的选项命名各厂商基本是自由发挥。3.1 固件层要先打开内核参数才有意义一个非常常见的困惑明明在内核参数里写了intel_iommuon重启后查日志却什么都没有。原因通常是固件里没开。Intel 平台在主板固件里的选项一般叫这几个名字之一VT-d或Intel VT for Directed I/OIntel Virtualization Technology for Directed I/O有些消费级主板藏在Advanced→CPU Configuration下面还有的干脆叫IOMMUAMD 平台的选项名一般是IOMMU或者AMD-Vi。ARM 服务器平台通常在固件里默认开启 SMMU没有单独的开关。需要额外注意一点很多消费级主板在开启 VT-d 之后会把选项旁边标注一行警告说可能影响兼容性这是厂商的保守表达。实际影响主要体现在老旧扩展卡上后面第六节会讲怎么处理。3.2 内核启动参数怎么写以及为什么常常带 iommupt最经典的写法是这两组# Intel 平台 intel_iommuon iommupt # AMD 平台 amd_iommuon iommuptiommuptpass-through的含义是对宿主机自己使用的设备使用恒等映射identity mapping也就是让 IOVA 直接等于物理地址不做真正的翻译。只有被交给虚拟机的设备才建立完整的翻译页表。为什么要这样因为对宿主机自己的设备来说地址翻译带来的隔离收益很小这些设备本来就在同一个信任域里但每次 DMA 映射都要改页表、发 IOTLB 失效、等确认在高频小包场景下这是实打实的开销。用iommupt可以把这部分开销压到最低同时保留完整的 IOMMU 框架直通设备照常工作。注意iommupt不是关掉 IOMMU它只是对宿主设备用恒等映射。设备直通的隔离能力、DMA 越界的拦截能力都还在。这个参数被误解过很多次包括我自己早期也以为它是个性能妥协开关。在较新的内核上这个参数有了新的表达方式# 新内核5.15 之后建议改用这两个 iommu.passthrough1 iommu.strict0两套写法的功能有重叠具体哪个生效取决于你的内核版本。稳妥的做法是先只用一组确认生效后再调整不要把一堆参数从网上抄一遍全塞进去出问题的时候根本不知道是哪个参数导致的。3.3 ARM 平台与内核编译选项ARM 服务器和部分嵌入式平台用的是 SMMUSystem MMU架构上对应 x86 的 IOMMU。开启方式不太一样设备树或者 ACPI 表里描述 SMMU 节点内核配置里打开对应驱动。关键的内核配置项有几个值得单独确认配置项作用说明CONFIG_INTEL_IOMMUIntel VT-d 支持x86 Intel 平台必需CONFIG_INTEL_IOMMU_DEFAULT_ON默认开启 VT-d决定不写参数时是否生效CONFIG_AMD_IOMMUAMD-Vi 支持x86 AMD 平台必需CONFIG_ARM_SMMU_V3ARM SMMUv3 支持ARM 服务器平台必需CONFIG_IOMMU_DEFAULT_PASSTHROUGH默认 passthrough相当于内核层写死iommuptCONFIG_IOMMU_DEFAULT_DMA_LAZY默认懒刷新影响性能和隔离强度的取舍CONFIG_IOMMU_SUPPORTIOMMU 框架总开关前面几项依赖它发行版内核一般已经把这些配好了自己编译内核的时候才需要逐项检查。判断方法很直接看/boot/config-$(uname -r)里对应项是不是y。grep -E CONFIG_(INTEL|AMD)_IOMMU|CONFIG_ARM_SMMU|CONFIG_IOMMU_SUPPORT /boot/config-$(uname -r)3.4 开启后怎么确认真的生效了参数写完、重启完别急着高兴先验证。三条命令就够# 看内核是否识别到 IOMMU 硬件 dmesg | grep -i -e DMAR -e IOMMU # 看 IOMMU 子系统是否注册成功 ls /sys/class/iommu # 看设备分组是否已经建立 find /sys/kernel/iommu_groups/ -maxdepth 1 -type d | sort -V第一条命令的正常输出里应该能看到类似这样的内容DMAR: IOMMU enabled DMAR: Intel(R) Virtualization Technology for Directed I/O DMAR-IR: Enabled IRQ remapping in x2apic mode如果只有固件的 DMAR 表信息没有IOMMU enabled这一行说明固件开了但内核参数没生效或者被别的参数覆盖了。如果连 DMAR 表都没打印那基本是固件层没开。第二条命令如果返回空说明 IOMMU 子系统没有注册任何实例。第三条命令返回的目录数量一般和平台相关几十个到上百个都正常——数量本身就是分组细粒度的体现。想看得更直观可以用这个脚本把每个 group 的成员列出来for g in /sys/kernel/iommu_groups/*; do echo IOMMU Group ${g##*/}: for d in $g/devices/*; do printf \t%s\n $(lspci -nns ${d##*/}) done done这个脚本我建议存成文件每次换插槽或者换卡都跑一遍比记在脑子里靠谱。4. 中断重映射最容易被忽略但最不能省的一环聊 IOMMU 的时候绝大多数资料都在讲 DMA 地址翻译很少有人把中断重映射Interrupt Remapping单独拎出来说。但它在直通场景里的重要性不比地址翻译低而且它是很多参数明明加对了、直通就是不工作问题的真正原因。4.1 MSI/MSI-X 中断本身就是一次内存写这一点是理解中断重映射的钥匙。现代设备用的 MSI/MSI-X 中断本质上是设备往一个特定地址写一个特定数据这个写操作由芯片组转发给 CPU 的中断控制器最终触发一个中断向量。既然是内存写那它天然就要经过 IOMMU 的检查。问题来了如果 IOMMU 开启了地址翻译但中断重映射没开设备发出的中断写会带着什么样的地址这个地址由谁翻译、翻译成什么没有中断重映射的时候这套机制是不完整的芯片组只能按固定规则处理结果是中断可能被送到错误的 CPU、错误的目标或者在虚拟化场景下被注入到错误的虚拟机里。更严肃的问题在于安全。中断注入如果不受控理论上一个被直通给虚拟机的设备可以通过构造特定的中断写来影响宿主机或者其他虚拟机的中断处理流程。中断重映射的作用就是给每个中断源分配一个标识让中断控制器能验证这个中断确实来自这个设备确实应该投递给这个目标。4.2 中断重映射不开时直通会出什么问题实际表现通常是这几类虚拟机启动时直接报vfio: failed to enable interrupt remapping直通设备起不来设备能直通进去但中断收不到虚拟机里表现为网卡收包卡死、阵列卡 IO 超时直通设备能工作但高负载下偶发中断丢失日志里出现irq 某某: nobody cared宿主机在设备热插拔时出现中断相关的告警内核日志里验证中断重映射是否开启看这一行dmesg | grep -i DMAR-IR # 正常输出 # DMAR-IR: Enabled IRQ remapping in x2apic mode如果没有这一行或者显示xapic mode之外的内容就需要检查中断控制器的工作模式。4.3 x2apic 与 IRQ 重映射的搭配关系中断重映射和 CPU 的中断控制器模式是绑定的。传统 xAPIC 模式下的中断投递方式比较受限能表达的 CPU 数量有限。x2APIC 模式扩展了寻址能力也是中断重映射正常工作更常见的搭配。如果内核日志里显示中断重映射没启用可以顺着这几个方向查固件里是否有独立的中断重映射选项被关掉了多数平台跟随 VT-d/IOMMU 总开关内核参数里有没有intremapoff这个参数一般是排查问题时临时加的很容易忘了删CPU 是否处于 x2APIC 模式dmesg | grep -i x2apic能看到内核配置里CONFIG_IRQ_REMAP是否开启# 临时排查时才会用的参数正常情况下不要加 # intremapoff提示intremapoff这类参数在社区帖子里经常作为解决某个奇怪问题的偏方出现。如果你的目标是设备直通不要用这个偏方它会让直通方案失去必要的基础。看到别人帖子里有这个参数先想想他的目标是不是和你一样。5. 性能账IOMMU 的开销究竟在哪里关于 IOMMU 的性能影响网上流传的说法跨度极大——有说几乎没有影响的也有说吞吐掉三成的。两个说法都有道理因为它们的测试场景完全不同。搞清楚开销的来源比记住某个百分比有用得多。5.1 DMA 映射与 IOTLB 的基本开销IOMMU 的性能成本主要来自三个环节第一是映射建立和销毁。驱动调用 DMA 映射接口时内核需要在 IOMMU 页表里找到空闲的 IOVA 区间建立页表项。设备用完再解除映射又要回收区间、清页表项。这部分是 CPU 开销和映射频率成正比。第二是 IOTLB 失效。IOMMU 内部有自己的地址翻译缓存IOTLB类似 CPU 的 TLB。页表改了以后缓存里的旧条目必须失效否则设备会用到过期的翻译结果。失效操作需要发命令给 IOMMU 硬件并等待确认这个等待在高频场景下会累积。第三是 IOTLB 缺失时的查表。设备发起的 DMA 地址如果不在 IOTLB 里IOMMU 要自己去内存里走一遍页表多级页表就是多次访存。这个延迟直接加在设备的 DMA 路径上。对照来看就很清楚映射次数少、每次数据量大的场景比如大块顺序 IO、大帧网络传输IOMMU 开销几乎可以忽略映射频繁、每次数据量小的场景比如高频小包转发、大量小随机 IO开销才会显现。5.2 strict 与 lazy 两种刷新策略的取舍内核提供了一个直接影响这个开销的开关失效操作的执行时机。**严格模式iommu.strict1长期是默认值**下每次解除映射都会立即执行 IOTLB 失效并等待完成。安全边界很清晰映射一解除设备马上就无法访问那块内存。懒模式iommu.strict0或者内核配置里的CONFIG_IOMMU_DEFAULT_DMA_LAZY下失效操作会被批量延迟执行一段时间。好处是大幅度减少了等待次数性能提升在部分场景下很明显代价是存在一个时间窗口映射已经解除了但 IOMMU 的缓存里还留着旧条目设备在这段时间里仍然能访问那块刚被释放的内存。这个窗口期意味着什么在设备被移除、内存被回收、重新分配给别的用途的场景下理论上存在被旧设备访问到的可能。所以在做设备热插拔、直通设备动态回收这类操作时lazy 模式需要额外注意。# 严格模式默认安全优先 iommu.strict1 # 懒模式性能优先需要评估风险 iommu.strict0我的建议是如果这台机器上有不受信任的工作负载或者你会做设备热插拔用严格模式如果是一台纯性能导向的单用户机器可以试懒模式但要清楚你放弃了什么。有些发行版在新版本里把默认改成了懒模式升级内核之后性能突然变好或者某些奇怪问题突然出现可以先查一下这个默认值有没有变。5.3 大页与 ATS/PASID让 IOMMU 少干活除了改刷新策略还有几层手段可以减少 IOMMU 的工作量使用大页映射。IOMMU 页表支持 2MB 甚至 1GB 的大页。同样一块内存用 2MB 页只需要一个页表项用 4KB 页要五百多个。页表项少了覆盖同样地址范围所需的 IOTLB 条目也少缺失率自然下降。这一条在设备 DMA 访问大块连续内存时效果明显。ATSAddress Translation Services。支持 ATS 的设备可以在自己内部缓存地址翻译结果需要时直接问 IOMMU 要一份之后就不用每次都走 IOMMU 查表了。这是把翻译成本从每次访问摊薄到每次缓存填充。前提是设备和 IOMMU 都支持且固件正确描述。PASIDProcess Address Space ID。让设备的请求带上进程标识实现设备和 CPU 共享同一套地址空间SVA。对 GPU、加速卡这类需要和用户态进程直接交换数据的设备价值很大普通网卡和存储卡用不上。实际能用到哪一层取决于硬件代次。近几年的服务器平台基本都支持 ATS消费级平台支持情况参差不齐查看方式可以从lspci -vv里找ATS相关的 Capabilities 字段。5.4 哪些负载真正受影响哪些是心理作用给几个具体的判断依据方便你对号入座场景IOMMU 开销感受说明大文件顺序读写基本无感映射次数少单次数据量大数据库随机小 IO轻微取决于 IO 深度通常低于 5%万兆网络常规收发包轻微使用iommupt后更小25G/100G 小包转发明显需要关注大页和刷新策略NVMe 直通给虚拟机轻微到中等取决于队列深度使用 bounce buffer 的老设备严重这才是真正的性能杀手最后一行单独说一下。有些老设备只有 32 位 DMA 寻址能力而系统内存大于 4GB内核就必须用 bounce bufferSWIOTLB——把数据先拷到低地址区域再让设备访问。这个拷贝开销远大于 IOMMU 本身的翻译开销。开启 IOMMU 之后内核可能更倾向于使用 SWIOTLB 而不是自己想办法绕过去所以在这种老设备上开启 IOMMU 后的性能下降主要来自 bounce buffer而不是翻译本身。# 看是否启用了 SWIOTLB 以及相关统计 dmesg | grep -i swiotlb cat /sys/kernel/debug/swiotlb/io_tlb_nslabs 2/dev/null如果你的场景里出现了 SWIOTLB优先考虑换设备或者加装支持 64 位 DMA 的控制器而不是纠结 IOMMU 参数。6. 开启后翻车的几种典型情况和排查链路IOMMU 属于那种平时不出声、出事就让人一头雾水的硬件特性。这一节把几种常见的翻车现场和排查顺序整理出来遇到问题时按顺序走比盲目搜帖子快得多。6.1 开了之后进不去系统或者卡在启动阶段第一种情况是加了参数之后系统起不来。表现可能是启动卡在某个驱动初始化、黑屏、或者直接崩到救援模式。原因通常是固件实现的 IOMMU 有缺陷内核在建立初始映射时失败。处理顺序先只加一个参数。不要一次把intel_iommuon iommupt iommu.strict0全加上只加intel_iommuon看能不能起来。能起来的话再逐个加后面的。加iommusoft试试。这个参数让内核使用软件实现的 SWIOTLB 代替硬件 IOMMU绕过有问题的固件实现。代价是性能损失但能让系统先跑起来。检查有没有和虚拟化相关的参数冲突。有些平台的固件在开启虚拟化扩展和 IOMMU 时行为不一致。确认固件版本。主板厂商的固件更新日志里经常有修复 IOMMU 相关问题这种条目遇到诡异现象值得先去官网查一下。# 现代 Linux 用这条在各启动项里临时追加参数做测试 # 在内核选择界面按 e 进入编辑在 linux 行末尾追加 intel_iommuon6.2 DMAR/IOMMU fault 日志怎么读如果系统能起来但运行中出现问题日志就是最主要的线索来源。一条典型的 fault 日志长这样DMAR: [DMA Read] Request device [03:00.0] fault addr fff00000 [fault reason 06] PTE Read access is not set逐段拆开看字段含义怎么用DMA Read访问类型读或写判断设备在做什么03:00.0设备 BDF用lspci -s 03:00.0定位是哪块设备fault addr出错地址结合驱动代码看这块地址应该属于谁fault reason原因码权限不足、页表项不存在、地址位宽不符等拿到 BDF 之后这一步一定要做lspci -s 03:00.0 -nn lspci -s 03:00.0 -vv | head -40第一条确认是什么设备第二条看它的 DMA 能力和链路信息。大多数 fault 日志指向的都是驱动 bug 或者设备固件 bug而不是 IOMMU 本身有问题。IOMMU 在这里扮演的是报警器把原本会静默破坏内存的错误暴露出来了。所以看到 fault 日志的第一反应不该是把 IOMMU 关掉而是终于抓到了。当然也有例外。如果 fault 是在设备正常工作时大量、规律地出现而且换到老旧内核上不出现那可能是内核驱动和 IOMMU 子系统的配合问题这种情况下去查对应驱动的更新记录比查 IOMMU 更有用。6.3 老设备、扩展卡和固件缺陷有几类设备在开启 IOMMU 之后特别容易出问题只支持 32 位 DMA 的老网卡、老阵列卡被迫走 bounce buffer性能骤降日志里能看到 SWIOTLB 相关输出固件里 ATS 描述有误的设备IOMMU 按固件描述建立映射描述错了就翻译错表现为间歇性数据传输错误PCIe 桥后面挂一堆设备的扩展卡分组混乱直通困难某些 USB 控制器和采集卡对映射约束有特殊要求容易出现映射失败这几类问题的共同处理思路是先用iommupt缩小影响范围如果iommupt下问题消失、完整翻译下问题出现那基本能确定是这个设备的映射能力问题可以考虑换设备或者对这块设备单独放宽约束。6.4 一套可复用的排查顺序把上面的经验整理成一个固定流程遇到 IOMMU 相关问题按这个顺序走确认是否真的开启dmesg | grep -i iommu看IOMMU enabled和DMAR-IR两行确认分组情况跑一遍分组脚本看目标设备是否独立成组确认中断重映射日志里有没有Enabled IRQ remapping看 fault 日志journalctl -k | grep -i -e DMAR -e IOMMU fault临时降级测试加iommupt或iommusoft看问题是否变化用来区分是翻译问题还是框架问题检查设备能力lspci -vv看 DMA 位宽、ATS、ACS 支持情况最后才考虑关掉把前六步的结论记下来再决定是否值得为了某个设备放弃 IOMMU这个顺序的核心逻辑是先缩小范围再下结论。跳过前面几步直接关掉 IOMMU等于把一个能帮你抓 bug 的工具扔掉下次遇到同样的问题还得从头开始猜。7. 到底该不该开按场景给出取舍建议回到标题本身的问题。经过前面这些展开答案其实已经很清楚了IOMMU 不是一个开了就变好的开关它是一个把设备访问从无序变成有序的基础设施。需不需要它取决于你的设备是否可信、是否要做设备隔离、以及你是否在意故障可观测性。7.1 家用 NAS 和轻量虚拟化这是最常见的场景。一台机器上跑着存储服务、几个容器、可能还有一两台虚拟机。建议开并且建议带上iommupt。理由有三点一是这类机器常插各种扩展卡网卡、HBA、扩展 SATA 控制器来源杂、质量参差DMA 越界的概率不低二是如果以后想把某块盘或者某块网卡直通给虚拟机开了就不用重装重新折腾三是iommupt已经把宿主设备的开销压到很低日常使用基本感觉不到差别。需要留意的是这类机器的固件往往是消费级主板VT-d 选项可能藏得比较深或者需要先开启虚拟化扩展才能看到 IOMMU 选项。7.2 多虚拟机宿主和长期运行的服务器必须开而且建议使用严格刷新模式。这类环境下隔离本身就是需求的一部分不是可选项。多个虚拟机共享同一套硬件如果设备之间没有 IOMMU 隔离一个虚拟机的设备理论上能读写另一个虚拟机的内存。这不是理论担忧而是实实在在的攻击面。同时这类环境对故障可观测性的要求也更高。生产机器上出现一个无法解释的数据损坏排查成本可能是几天的停机。有 IOMMU fault 日志在手定位时间能压缩到分钟级别。7.3 普通桌面和笔记本看情况。如果你是普通办公和娱乐用途不涉及虚拟化和设备直通开不开的差别主要体现在两方面一方面现代平台开启 IOMMU 之后还有一个不太被提及的收益——外部接口的 DMA 防护。笔记本的雷电接口、部分支持 PCIe 的外接扩展坞这些接口允许外部设备直接发起 DMA。在固件配合的前提下IOMMU 可以对这些外部设备的访问范围做限制。这是平台层面的一项防护能力不是软件能替代的。另一方面个别笔记本平台的固件在开启 IOMMU 后会出现休眠唤醒异常、外接显示器识别问题等。如果遇到这类情况可以先在固件里关掉再观察确认是不是 IOMMU 引起的。我的建议是能开就开遇到具体问题再单独处理。因为不开的话遇到问题连日志都没有只能靠猜。7.4 我的默认策略折腾了这些年我现在的默认做法是这样新装一台机器第一件事是进固件把虚拟化相关选项全部打开装完系统第一件事是确认dmesg里有IOMMU enabled和Enabled IRQ remapping in x2apic mode这两行。参数上先用intel_iommuon iommupt这套最保守的组合等有明确需求再考虑调整刷新模式。分区上留一个专门跑虚拟机的环境把可能要直通的设备网卡、HBA、显卡尽量插在 CPU 直连的 PCIe 槽上装机的时候就规划好省得后期为了调分组把机箱拆来拆去。踩过几次坑之后我越来越觉得IOMMU 这类基础能力最值得投入的地方不是调优而是**在有需要的时候它已经在那里**。很多硬件层面的问题有没有 IOMMU 决定了你是花两个小时定位还是花两天换零件。最后分享一个小习惯每次换硬件、换插槽、更新固件或者升级内核之后我都会把那三条确认命令跑一遍把分组脚本的输出存到一个文本文件里文件名带上日期。看起来有点多余但真的遇到问题时有一份之前是正常的的输出做对比排查效率完全不一样。
返回列表