ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

IOMMU深入解析:DMA隔离、设备直通与性能调优实践

IOMMU深入解析:DMA隔离、设备直通与性能调优实践 1. 从 DMA 讲起IOMMU 到底管的是哪一段IOMMU 这个缩写很多人第一次是在 grub 内核参数里撞见的intel_iommuon。照着网上的教程加上、重启、然后 dmesg 里冒出一堆 DMAR 开头的日志接着不管什么性能问题都开始怀疑它。这太常见了。先说清楚 IOMMU 是什么的它是 Input/Output Memory Management Unit输入输出内存管理单元。CPU 那边有 MMU 管虚拟地址到物理地址的翻译而 IOMMU 管的则是设备和内存之间的 DMA 访问。设备想读写内存时不再直接拿物理地址去访问而是先经过 IOMMU 这张门禁表做一次地址翻译和权限检查合法才放行。没有 IOMMU 的时代DMA 设备写内存就像小区里随便进出的快递车——只要拿到一个物理地址就能直接访问根本没人核对它有没有权限。这个设计在单机简单场景下没问题可一旦牵扯到虚拟化、安全隔离或者大内存麻烦就来了。为了方便理解可以把物理内存想象成一个仓库每个货位就是一个物理地址。CPU 是这家仓库的内部员工有工牌、有权限等级想去哪就去哪但也要过一道闸机MMU。外接设备比如网卡、GPU、NVMe 硬盘则像第三方物流的货车没有 IOMMU 时货车可以直接开进仓库乱翻有了 IOMMU仓库门口多了一个专门管货车的门卫每一辆车要去哪个货位、能去哪些货位、一次最多装卸多少都需要事先登记。换句话说IOMMU 本身不产生数据它只做四件事地址翻译、权限隔离、故障上报、中断重映射。这四个能力看似基础但直接影响着虚拟化性能、直通设备稳定性乃至整机安全。最容易被误解的一点是 IOMMU 和 MMU 的关系。MMU 管的是 CPU 视角的内存视图IOMMU 管的是设备视角的 DMA 视图两者各管各的互不替代。你的系统可以不开 CPU 虚拟化就是 BIOS 里那个 VT-x也可以没有任何虚拟机但 IOMMU 依然可以独立开启因为它的价值并不只服务虚拟化。还有一个高频问题为什么开了 IOMMU 之后某些设备反而报 DMA fault原因往往是设备驱动或者硬件自身没有按约定走 IOMMU 流程而不是 IOMMU 功能坏了。IOMMU 干的是严格执法的活它把以前被掩盖的非法 DMA 访问暴露出来了所以你会看到一排排的 Fault 日志。这一点在后面的排查章节里会详细展开。2. 为什么而开安全隔离、设备直通与中断重映射三大驱动力开启 IOMMU 不是没事找事。如果你只是普通桌面用户不打游戏、不跑虚拟机、不搞透传那开不开它的体验差异极小。但下面这三类需求任何一个出现IOMMU 都必须认真对待。2.1 安全隔离把 DMA 的任意门焊死这是 IOMMU 最初也最本质的动机。做了安全方向的人都清楚拿下 DMA 权限就约等于拿下了整台机器的内存。一个恶意或损坏的 PCIe 设备理论上可以构造任意 DMA 请求读取内核态敏感数据甚至往内核内存里写 shellcode。这类攻击在真实环境中是有成熟利用链的CTF 比赛里也经常出现。IOMMU 一开设备就只能访问被映射过的地址区间。对于普通驱动内核会为它申请 DMA buffer 的那块区域建立映射没映射过的地址设备一旦尝试访问IOMMU 直接阻断并上报 fault。这等于是在硬件层面给任意 DMA关上了门。所以很多对安全合规有要求的服务器不管虚拟化用不用都会强制开启 IOMMU。注意这里说的开启是指完整模式不能为了性能挂iommupt因为 pt 模式下隔离粒度会大打折扣这个后面会详细讲。2.2 设备直通虚拟化的性能救星虚拟化场景是 IOMMU 最广为人知的用武之地。具体来说当你希望虚拟机直接使用一块物理网卡、GPU 或者 NVMe 硬盘时数据通路就不再想经过宿主机内核的模拟层了。设备直通Device Passthrough意味着设备要直接响应虚拟机发出的 DMA 请求而虚拟机的内存物理地址往往是分散的设备看到的地址如果不经过翻译根本没法正确访问。没有 IOMMU 的时候虚拟化平台只能靠软件在宿主机内存里做 bounce buffer弹跳缓冲先把数据从 guest 内存抄到一片连续的物理内存里再让设备读取反过来再抄一次。数据来回拷贝CPU 占用高、延迟大、吞吐量上不去尤其是在万兆网卡、NVMe 这种高速设备上性能损失非常难看。有了 IOMMU事情就变得优雅了虚拟机里的设备驱动看到的地址经过 IOMMU 的翻译表映射到真实的物理地址设备直接访问数据零拷贝。这也正是 VFIO 框架的核心依赖——VFIO 的直通能力如果没有 IOMMU 支撑连设备分组都做不起来。可以说没有 IOMMU 就没有现代意义上的高性能设备直通。2.3 中断重映射容易被忽视的第四项能力IOMMU 不光管 DMA 数据还管中断。Intel VT-d 规范里的 Interrupt Remapping 功能会把设备的 MSI/MSI-X 中断请求重新映射到宿主机指定的中断向量上。这在虚拟化场景里尤其重要否则设备直通后虚拟机可以直接操控中断注入往宿主机塞伪造的中断请求这就是一个很经典的逃逸攻击面。中断重映射启用后dmesg 里会看到类似DMAR: Interrupt remapping enabled的日志。有些场景里你只是想让 IOMMU 做 DMA remapping不想承担中断重映射带来的复杂性也可以通过nointremap内核参数关闭它。我个人的建议是不要轻易关除非你有非常具体的兼容性问题需要排查。3. 开启 IOMMU 的完整实操BIOS、内核参数、验证命令这个部分写给第一次接触 IOMMU 的人已经折腾过的人可以直接跳到 4、5 节。先强调一句IOMMU 的开关不是一个开关而是BIOS 开关 内核参数 设备驱动配合三层链路任何一层设置不对最终效果都可能打了折扣。3.1 BIOS 层面VT-d 还是 AMD-Vi别找错选项Intel 平台的主板IOMMU 的 BIOS 开关通常叫 Intel Virtualization Technology for Directed I/O缩写 VT-d。注意它和你开启 CPU 虚拟化扩展的那个 Intel Virtualization TechnologyVT-x是两码事位置也往往不在同一个子菜单里。有些厂商主板的 VT-d 藏在 Chipset 或 Advanced - Virtualization 配置下面有些 OEM 服务器甚至默认就把 VT-d 关闭了需要手动打开。AMD 平台对应的选项通常是 IOMMU 或者 AMD-Vi。这里有个常见误区AMD 平台开启虚拟化用的是 SVM Mode很多人在 BIOS 里看到 SVM 就以为 IOMMU 也开了实际上 SVM 是 CPU 虚拟化扩展和 IOMMU 不是一回事。AMD 平台的 IOMMU 开关要找独立选项某些微星、华擎主板上它叫 Overclocking 菜单下的 IOMMU 子项位置比较迷找不到就用主板说明书搜索 AMD-Vi 或 IOMMU 关键词。3.2 内核参数intel_iommuon 和 iommupt 怎么选BIOS 开好之后Linux 侧还需要通过内核 cmdline 显式开启。大多数现代发行版在 intel 平台默认是不开的需要手动加参数。常用参数组合如下参数组合效果适用场景intel_iommuon完整开启 IOMMU含 DMA remapping 与隔离推荐默认使用安全性最好intel_iommuon iommupt启动 IOMMU 但使用 pass-through 模式对性能敏感、安全要求不高的场景amd_iommuonAMD 平台开启 IOMMUAMD 平台默认使用nointremap关闭中断重映射排查中断相关兼容性问题时临时用igfx_off关闭内核对 iGPU 的 IOMMU 支持解决多 GPU 直通时的 iGPU 异常修改方式是在/etc/default/grub里的GRUB_CMDLINE_LINUX中追加然后重新生成引导配置。Debian/Ubuntu 系执行update-grubRHEL/CentOS 系执行grub2-mkconfig -o /boot/grub2/grub.cfg。如果你用的是 UEFI 引导路径可能换成/boot/efi/EFI/distro/grub.cfg保险起见先看当前系统实际读取的 grub.cfg 路径。3.3 验证到底开没开不要只看 dmesg 的一句话装完参数重启后第一件事不是急着跑虚拟机而是确认核心功能真的生效。执行下面几条命令dmesg | grep -e DMAR -e IOMMU ls /sys/kernel/iommu_groups/ | head第一条如果有DMAR: IOMMU enabled这类输出说明 IOMMU 软件层面已经工作。第二条如果列出一堆数字目录比如 0、1、2、3 这种 group 编号说明内核已经为设备分配了 IOMMU 域。这个命令非常重要后面 4 节会反复用到。一个容易踩的坑ls /sys/kernel/iommu_groups/返回空但dmesg里显示 IOMMU 已启用。这种情况通常是因为 BIOS 没开 VT-d/AMD-Vi或者内核 cmdline 本身没有生效比如 grub 配置写错位置。只有 IOMMU 正常启用/sys/kernel/iommu_groups/才会真正填充内容我遇到过很多次看了 dmesg 以为开了结果 iommu_groups 目录空空如也的情况所以验证务必两条命令一起看。4. IOMMU group 与 VFIO直通设备的真正门槛很多做设备直通的人都有这个体验按照网上的教程把网卡加了 vfio-pci重启后 QEMU 启动时仍然报错 operation not permitted或者设备明明在 lspci 里能看到却无法从 vfio-pci 驱动接管。排查到最后十有八九是 IOMMU group 的问题。4.1 为什么直通的最小单位是 group 而不是单个设备IOMMU 的分组逻辑是这样的物理上处于同一个 PCIe 拓扑比如同一个 Root Port 下的所有设备或者同一个多功能设备的所有 Function下的设备通常会被划分到同一个 IOMMU group 里。这个 group 是硬件层面能够实现安全隔离的最小单元也就是说同一组里的设备共享同一个 IOMMU 保护域。为什么要按组来分因为如果两个设备在硬件上共享同一个 DMA 重映射单元就不可能只放行其中一个、拦截另一个安全性无从保证。硬着头皮把其中一个设备直通给虚拟机另一个留在宿主机理论上可能通过 PCIe 的某种机制互相干扰内核为了安全直接禁止这种行为。这个宁可不可用不可不安全的设计原则贯穿 VFIO 始终。所以当你看到某个设备和一个不相关的 USB 控制器、声卡绑在同一个 group 里不是内核分配失误而是硬件拓扑决定的。查看分组的方法很简单find /sys/kernel/iommu_groups/ -type l | sort输出会列出每个 group 下挂着的设备 BDF比如0000:00:1f.2。如果目标设备的 group 下面只有它自己直通就顺利如果还挂了一堆别的设备那么这些设备要么一起直通到同一台虚拟机要么想办法拆分 group。4.2 拆分 group 的思路ACS 补丁的适用范围当一个 group 里设备混着太多无关设备时最常用的处理手段是加pcie_acs_override参数。这个参数告诉内核我理解 ACSAccess Control Services没有在硬件层保证隔离但我依然允许拆开分组。完整的写法通常是pcie_acs_overridedownstream,multifunctiondownstream允许拆分下游端口上的设备multifunction允许拆分同一个 PCI 多功能设备下的不同 Function。这是我处理多 GPU 直通时非常依赖的参数没有它很多消费级主板的 x16 插槽和旁边的 USB 控制器根本拆不开。强调一点pcie_acs_override是在牺牲安全隔离的前提下换取可用性生产环境里要谨慎使用。如果你在做的是测试机或者个人工作站那完全没问题如果是对多租户隔离有要求的服务器还是优先考虑购买带 ACS 支持的服务器级主板或者干脆买多台机器。4.3 把设备绑定到 vfio-pci 的标准流程IOMMU group 没问题之后设备直通的技术栈就比较固定了。第一步是加载 vfio 相关模块modprobe vfio-pci modprobe vfio_iommu_type1第二步是确认目标设备的 vendor ID 和 device ID然后绑定 vfio-pci。可以用driver_override这种方式也可以直接操作 sysfsecho 0000:01:00.0 /sys/bus/pci/devices/0000:01:00.0/driver/unbind echo 8086 10fb /sys/bus/pci/drivers/vfio-pci/new_id这手动的过程适合排查问题但每次开机都要做一遍。所以生产上我更推荐通过内核模块参数预绑定在/etc/modprobe.d/vfio.conf里写options vfio-pci ids8086:10fb,10de:1e84 softdep nvidia pre: vfio-pci这样开机时系统会先加载 vfio-pci 并接管指定设备避免原生驱动抢占。这里有一个常见的坑预先 blacklist 掉 nvidia、i915 这些驱动的行为有时候反而会引起问题因为 N 卡驱动需要用到的并非只有 PCI 设备本身。软依赖softdep方式比直接 blacklist 更温和可控推荐优先用。绑定完成后可以在 QEMU 命令行里直接把设备挂给虚拟机-device vfio-pci,host01:00.0,multifunctionon如果执行到这一步顺利跑起来说明 IOMMU、VFIO、IOMMU group 一整条链路都是通的。剩下的问题基本集中在设备固件、中断路由和性能调优上这就轮到第 5 节了。5. IOMMU 的代价性能损耗、pass-through 模式与常见调优IOMMU 不是免费午餐。每一步 DMA 访问多经过一层地址翻译必然要付出性能代价。关键是这个代价有多大、能不能接受、怎么降到最低。5.1 损耗从哪来页表遍历、TLB 未命中、缓存维护IOMMU 自己有一套页表叫做 I/O Page Table查找这个页表的过程对设备侧来说是额外的指令周期。更关键的是 TLB这里特指 IOTLBIOMMU 的页表缓存当设备频繁访问大量分散的内存页时IOTLB 会不断 miss每次 miss 都要回内存里查页表延迟会明显上升。高速网卡的 DMA 操作非常密集小包场景下每秒可能产生数百万次 DMA 请求IOTLB miss 的比例一旦上去吞吐量就绷不住。实测中在一些没有开启大页、没有优化映射的机器上开 IOMMU 后包处理性能下降个百分之二三十并不罕见。这个数字取决于具体网卡、驱动压测方法和内核版本不必当作绝对标准但它足以说明问题IOMMU 的性能损耗不能忽视。另一个容易被忽视的开销是缓存一致性维护。处理器在修改 IOMMU 页表后需要发送队列无效化命令invalidate queue让 IOMMU 更新 IOTLB。如果页表更新频繁比如驱动频繁映射/解映射 DMA buffer这部分开销会挤占正常 DMA 的带宽。所以很多高性能网络场景的调优都会强调避免频繁的 DMA 映射操作尽量复用已映射的 buffer。5.2 pass-through 模式的本质用隔离换性能iommuptpass-through 模式就是在知晓代价之后做出的平衡选择。这个模式下IOMMU 仍然工作但它的页表映射退化成 identity mapping也就是设备 DMA 地址直接等于物理地址免去了复杂地址翻译的过程。中断重映射、fault 上报这些功能保留但隔离粒度变粗了设备之间无法通过 IOMMU 做到严格的内存访问隔离。到底该用iommupt还是完整模式我的经验如下场景推荐配置理由生产服务器、多租户环境intel_iommuon完整模式安全隔离是第一优先级性能可通过大页等手段缓解DPDK 高性能转发iommupt 大页转发性能敏感且通常设备直接绑定用户态驱动显卡直通intel_iommuon显卡 DMA 模式多为大批量连续传输损耗影响有限内网资源有限的集群完整模式 数据面优化避免故障时安全审查风险后续运维省心一个很常见的误区是直通 GPU 也非要用iommupt。其实对于 GPU 这种大块连续 DMA 的场景IOMMU 翻译的额外开销占比很小开完整模式完全够用反而是iommupt会降低设备隔离能力得不偿失。DPDK 场景用 pt 模式则是合理的因为小包处理对每一笔 DMA 的延迟都极其敏感。5.3 直接可抄的调优清单我在实际环境中反复验证过几组调优手段按性价比排序开启 hugepages2MB 甚至 1GB 大页减少 IOMMU 页表遍历层级。IOMMU 对大页映射的处理开销远小于 4KB 小页面这一条收益最直接。把 dmesg 里 IOMMU 域的大小尽量保持稳定。驱动反复 map/unmap 会让页表频繁变更必要时可以用 DPDK 等用户态驱动来替代传统内核驱动把 DMA 映射集中管理。确认内核启用了CONFIG_INTEL_IOMMU_DEFAULT_ON相关的编译选项部分发行版默认未开启需要重新编译内核才能使用某些高级特性。对纯网络转发服务器可以在完整模式基础上单独为 DPDK 网卡开启 VFIO而不是整个系统切换 pt 模式。这样既能保住安全隔离又能获得接近零拷贝的性能。如果你看到 IOMMU 开启后 NVMe 性能也有轻微下降这同样是页表翻译的代价正常现象。不要因为 1%-2% 的损失就关掉整个功能很多系统管理员过于激进地关闭 IOMMU结果在后续遇到内存隔离问题时反而更难收场。6. 实战排查实录DMAR fault、组拆分与内核参数调试链很多人在 IOMMU 出问题时第一条反应就是上网搜报错然后被各种假教程带偏。这一节我分享一套自己的排查链路几乎能覆盖八成以上的 IOMMU 故障。6.1 典型日志解读DMAR fault 到底在说什么最常见的 IOMMU 报错长这样DMAR: DRHD: handling fault status reg 3 DMAR: [DMA Read] Request device [01:00.0] fault addr 0x1234000 [fault reason 0x05] PTE not present逐字段拆解一下。DMA Read表示这是一次读 DMA 请求[01:00.0]是设备 BDFfault addr是设备尝试访问的地址fault reason 0x05是 IOMMU 返回的故障原因码最常见的是 PTE not present表示设备访问了一个没有建立映射的地址。看到这条日志不要急着怪 IOMMU。先确认这个设备是不是已经被正确绑定到某个驱动vfio-pci 或原生驱动驱动是否已经为 DMA 操作申请并映射了内存。如果驱动没有映射就发起 DMAIOMMU 就会给这个 fault这在本质上是一个驱动 bug 或设备固件 bug 的暴露。排查顺序可以是这样dmesg | tail -100 ls -l /sys/kernel/iommu_groups/*/devices/ dmesg | grep -i -e vfio -e iommu -e dmar如果设备在 vfio-pci 下依然报 fault最常见的原因是 QEMU 分配 guest 内存时没有正确传给 VFIO或者 Linux 的 hugepages 分配不足导致映射归一了内存页。此时可以尝试在 QEMU 里加-object memory-backend-file,preallocon这类参数强制预分配内存减少运行时页表切换。6.2 多设备同组拆不开ACS 参数的合理使用有一次我给两台 GPU 做直通lspci 清清楚楚是两个独立设备但 iommu_groups 里它俩死活在同一个 group 下。反复确认 BIOS 已经没有相关开关最后用pcie_acs_overridedownstream,multifunction解决的。加参数有一个前提你必须确认主板 PCIe 拓扑确实不支持 ACS。怎么确认lspci -vvv -s 01:00.0 | grep -i acs如果输出空白说明该端口不支持 ACS。这时加pcie_acs_override是合理的如果输出里明确写着ACSCap: ...那也许只是内核没读全可以用pcie_acs_overridedownstream试探。我见过一些工人主板在 BIOS 更新后 ACS 能力被隐藏需要非常旧的固件才能完整暴露这类限制就别死磕了换坑位比刷 BIOS 更实际。另外pcie_acs_override参数对直通本身不是必须的它只是让你能在不合规的拓扑上强行拆分 group。即便不开这个参数一些支持 ACS 的设备 group 是能自然分开的所以先查lspci -vvv再决定动不动建议参数优先级更高。6.3 两个容易误导的隐藏问题iGPU 与 AER 报错IOMMU 常见的幻影故障还包括集成 GPU 和 AERAdvanced Error Reporting。iGPU 在没有正常初始化的情况下一旦尝试 DMA可能会触发 DMAR fault导致系统日志刷屏而实际功能影响不大。此时用igfx_off关闭 iGPU 的 IOMMU 支持即可它不影响独立显卡直通。AER 报错则是另一个高频干扰源。dmesg里出现PCIe Bus Error: severityCorrected且伴随DMAR字样时很多人会误判成 IOMMU 问题。实际上这是 PCIe 链路层的错误报告和 IOMMU 没有直接关系。处理方法是先关掉CONFIG_PCIEAER或者找到对应驱动禁用它再观察 DMAR fault 是否消失。如果 AER 报错设备本身已经直通给虚拟机那日志主要作为链路稳定性参考大可不必为了刷干净日志重装整个系统。6.4 内核 cmdline 优先级与 grub 链路的最后检查最后一条经验写给改了内核参数但没有效果的兄弟们。很多新手把参数加在/etc/default/grub的GRUB_CMDLINE_LINUX_DEFAULT里也执行了update-grub重启后cat /proc/cmdline一看参数没进去。原因多半是 grub 配置文件被覆盖或者系统使用的是 BCDWindows 引导管理器 Linux 的 WSL2 双系统场景或者主板 UEFI 里同时存在多个引导条目实际引导的并不是你 update-grub 的那个。解决方法是先手动确认当前引导使用的配置cat /proc/cmdline如果参数缺失检查/boot/grub2/grub.cfg或/boot/efi/EFI/*/grub.cfg里是否真的包含了新参数。部分发行版可能还需要grub2-mkconfig -o /boot/efi/EFI/distro/grub.cfg并重启一次才能让 UEFI 引导条目刷新。这里最容易出错的是 UEFI 启动时 grub.cfg 路径和传统 BIOS 启动路径不一致很多时候你执行的 update-grub 更新了 A 路径的配置而系统实际读取的是 B 路径。如果cat /proc/cmdline已经确认有intel_iommuon但dmesg仍然看不到 IOMMU enabled再检查一遍 BIOS 开关。注意部分 OEM 服务器在启用虚拟化之后还需要额外确认 I/O 虚拟化开关不是同一个选项漏掉任何一个都会导致 IOMMU 不生效。最后再分享一个小技巧我排查 IOMMU 问题时习惯性地会先执行find /sys/kernel/iommu_groups/ -type l | wc -l看看到底有多少个 group。正常情况下这个数字应该和主板 PCIe 拓扑中的独立 Root Port 数量相当。如果 group 数量过少说明设备都挤在同一组里直通基本没法做如果 group 数量异常多反而要小心驱动的过滤问题。这比单纯看 dmesg 直观得多至少在虚拟化直通这条赛道上IOMMU group 的数量和分布就是一切问题的最短路径。
返回列表