尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux开启IOMMU全攻略:BIOS到内核参数,搞定KVM直通与DMA安全

Linux开启IOMMU全攻略:BIOS到内核参数,搞定KVM直通与DMA安全 最近折腾KVM虚拟化本来想把手头一张网卡直接直通给虚拟机做软路由结果virsh attach-device一执行宿主机直接没了响应journalctl里翻到的是密密麻麻的DMA fault报错。排查到最后一拍大腿——IOMMU这个总开关根本没开。IOMMU在Linux里是个很容易被忽略、但一旦用到虚拟化设备直通就绕不开的东西。它管的是设备对物理内存的访问权限通俗点说就是在PCIe设备和内存之间装了个门卫没有它设备驱动说访问哪块内存就能访问哪块有了它每个设备只能动你分给它的那一亩三分地。这篇文章我就把Linux开启IOMMU的完整套路掰开揉碎讲一遍从BIOS设置到内核引导参数再到验证手段给正在折腾KVM/QEMU直通、SR-IOV或者单纯想做DMA安全隔离的朋友当个开路参考。1. 什么时候必须碰IOMMU这个开关——适用场景与收益1.1 最典型的场景虚拟机里的PCIe设备直通但凡你用过或者听说过大名鼎鼎的GPU直通给Windows虚拟机塞一张物理显卡就一定会遇到IOMMU。QEMU/KVM里要想把PCIe设备完整地交给虚拟机使用走的是VFIO框架而VFIO依赖IOMMU来做DMA地址重映射。你可以把PCIe直通想象成把一个房间的钥匙交给房客IOMMU负责把房间里的所有出入口都换成带权限锁的门否则房客能顺着走廊跑到别的房间里。有很多朋友在论坛里贴报错说VFIO_MAP_DMA失败或者虚拟机一启动就卡在PCI设备初始化十有八九就是IOMMU没开或者没被内核正确识别。这种情况下先去查IOMMU状态比在QEMU命令行里反复调选项节省几个小时。1.2 IOMMU同时也是宿主机安全的“门禁”除了直通IOMMU还有一个作用容易被忽视——抵御DMA攻击。插在PCIe插槽上的设备理论上拥有较高的总线权限恶意设备或者被攻破的网卡固件可以通过DMA直接读写物理内存绕过CPU的一堆安全机制。IOMMU启用后设备DMA操作必须先经过地址翻译和权限检查等于加了一道硬防火墙。企业环境里做安全审计的时候IOMMU是否开启经常会作为一个固定资产项被问到。1.3 不开启到底会看到什么现象先给各位一个症状对照表方便排查的时候快速定位场景不开IOMMU的典型表现PCIe设备直通给虚拟机VFIO_MAP_DMA失败、QEMU启动阶段卡死、Guest内设备IO异常SR-IOV虚拟功能VF分配无法把VF挂载到VM上或者VF初始化时DMA报错DMA重映射/安全隔离内核日志中无DMAR: IOMMU enabled字样无法通过安全基线检查多GPU/多网卡直通设备直通后性能暴跌、间歇性死机、收发包异常所以这篇博文的核心受众就很明确了KVM/QEMU用户、SR-IOV玩法爱好者、做安全加固的运维朋友以及所有准备让物理设备和虚拟机“共享”的折腾党。2. 先摸清家底CPU、主板、内核三方支持度确认开启IOMMU不只是改一个配置的事它需要CPU、主板固件、Linux内核三方配合。我见过太多人上来就改GRUB改完重启发现还是老样子最后发现是BIOS里压根没开或者CPU型号不支持。所以先做三方检查。2.1 检查CPUIntel VT-d还是AMD-ViIntel平台上的IOMMU实现叫VT-dVirtualization Technology for Directed I/OAMD平台的叫AMD-Vi有时候也直接叫IOMMU。不管是哪家几乎所有2015年之后的服务器/桌面CPU都支持但低端切割产品线里偶尔会有例外。CPU支持情况可以通过lscpu和/proc/cpuinfo检查虚拟化指令集grep -E -o (vmx|svm) /proc/cpuinfo | sort -u如果输出里同时包含vmxIntel或者svmAMD说明CPU虚拟化基础特性在但这只是支持VT-x/AMD-VCPU虚拟化的标识和IOMMU还不完全等价。想进一步确认Intel平台的VT-d能力更可靠的命令是用dmesg或ls /sys/class/iommu/——如果系统固件已经暴露了IOMMU即使内核未启用也可能在这个目录能看到设备。另外Intel有官方工具intel-virtualization-technology检测工具不过实际使用中只要平台不是太老基本都支持。真正决定IOMMU开不开得了的往往在主板固件这一层。2.2 判断主板固件是否有戏主板固件层面看的是BIOS/UEFI里有没有VT-d或AMD-Vi的开关选项。服务器主板几乎必然有消费级主板这几年也基本全都有了但品牌机、笔记本的固件比较封闭有些会把这个选项藏起来甚至直接阉割掉搜索项。判断方法是重启进BIOS后在Advanced高级或Virtualization虚拟化类别下找关键词Intel平台搜VT-dAMD平台搜IOMMU或AMD-Vi。如果见不到但机器又不算太老优先去官网更新固件新版本固件里很可能补上了选项。注意BIOS里VT-d的开关和你CPU的VT-x开关经常是分开的两个选项。有些主板默认把VT-x开了VT-d反而是Disabled或Auto。别看见VT-x是Enabled就以为万事大吉。2.3 内核有没有编进IOMMU支持即使硬件全支持Linux内核如果没编入IOMMU相关驱动后面的一切都无从谈起。绝大多数主流发行版Debian/Ubuntu/RHEL系、Arch等的通用内核都默认开启了CONFIG_INTEL_IOMMU和CONFIG_AMD_IOMMU但自编译精简内核或者某些嵌入式定制内核可能没编。检查方式很直观grep -E CONFIG_(INTEL_IOMMU|AMD_IOMMU|IOMMU_SUPPORT) /boot/config-$(uname -r)如果输出里能看到CONFIG_INTEL_IOMMUy或者CONFIG_AMD_IOMMUy内核这关就算过了。要是显示# CONFIG_INTEL_IOMMU is not set那就得换个发行版内核或者重新编译内核这不是改个参数能解决的。三关都过才到了真正动手的时间点。3. 固件层面打开通道BIOS/UEFI里如何设置VT-d与AMD-Vi3.1 VT-d和VT-x的区别别搞混有些朋友在BIOS里只开了Intel Virtualization TechnologyVT-x就跑回来问我为什么intel_iommuon不起作用。VT-x是CPU虚拟化指令扩展管的是CPU资源隔离而VT-d管的是设备DMA重映射两者是独立的。绝大多数主板上也是两个分开的菜单项一个叫Intel(R) Virtualization Technology另一个叫Intel(R) Virtualization Technology for Directed I/O (VT-d)后者才是IOMMU。AMD平台类似SVM Mode管CPU虚拟化IOMMU或AMD-Vi才是设备重映射。3.2 不同厂商固件入口速查我在华硕、技嘉、微星、超微、戴尔几类主板上都操作过入口位置差异比较大给你一个速查表格主板类型常见路径设置项名称华硕消费级Advanced → CPU Configuration / North BridgeIntel VT-d / AMD IOMMU技嘉消费级Settings → Miscellaneous / IO PortVT-d / AMD CBS → NBIO → IOMMU微星消费级Overclocking → CPU Features / Settings → AdvancedIntel VT-d / AMD IOMMU超微服务器Advanced → PCIe/PCI/PnP ConfigurationVT-d / AMD IOMMUDell服务器System BIOS → Integrated DevicesVT-d / IOMMU SupportHPE服务器Advanced → PCI Device OptionsIntel VT-d / AMD IOMMU设置好了先别急着进Linux顺手把Above 4G Decoding或Above 4G MMIO BIOS Assignment也打开。这个选项平时用不到但做PCIe直通时很有用。有些显卡或者NVMe控制器的MMIO BAR比较大不开这个选项直通后地址空间不够设备会死气沉沉。3.3 “找不到IOMMU开关”的几种情况怎么破固件界面没有文字搜索功能那就一个菜单一个菜单翻优先看Advanced、North Bridge、PCIe、Virtualization这几个大类。整个BIOS里压根没有VT-d/IOMMU字样先更新固件到最新版OEM厂商经常在新版固件里开放更多选项。更新完还是找不到那就说明这块主板的固件层面不支持或故意隐藏了要么接受现实要么去社区查查有没有外传的隐藏菜单方法——但我不建议为了一个硬件开关去刷修改版BIOS稳定性风险太高。BIOS里已经Enabled了但Linux下仍然无效不用慌这正是下一章要讲的内容。固件打开只是第一步Linux内核侧还需要显式接收这个开关。关键认知BIOS开启IOMMU只是在硬件层面把功能暴露出来Linux默认启动时不会自动启用DMA重映射还得靠内核引导参数去激活。4. Linux系统层真正开闸GRUB引导参数配置全流程这一章是整个操作的核心也是标题里“方法”二字的正主。一共有两个阶段给内核加引导参数然后重新生成引导配置并重启。4.1 加参数之前先搞清楚该加什么根据CPU平台不同分别用对应的内核参数Intel平台intel_iommuonAMD平台amd_iommuon通用参数iommupt我在实际使用中还会再加上iommupt。pt是pass-through的缩写意思是“对于不做直通的设备直接绕过IOMMU重映射”。这个参数需要解释一下IOMMU启用后默认情况下所有PCIe设备的DMA操作都要经过页表翻译这会有少量性能开销。对网络吞吐、NVMe读写这种数据面操作来说哪怕几个百分点的性能损失也不划算。加iommupt后只有你明确绑定给VFIO的设备走IOMMU其余设备保持直通模式性能几乎无损。Intel平台官方文档里iommupt也是推荐项AMD平台同样适用。另外还有一个可选项intel_iommuon,igfx_off——如果直通的是Intel核显有的平台上默认会把图形设备的GFX引擎也做DMA重映射导致Guest里显卡无输出加上这个参数可以关掉对核显的IOMMU干预。如果直通的是独立NVIDIA/AMD显卡则不需要这个参数。4.2 实测不同发行版的修改路径与刷新命令主流发行版改GRUB配置的套路有一些差异我按Debian系、RedHat系、Arch系分别记录一下。首先都是打开/etc/default/grub找到GRUB_CMDLINE_LINUX_DEFAULT这一行在引号内追加参数# Intel平台 GRUB_CMDLINE_LINUX_DEFAULTquiet splash intel_iommuon iommupt # AMD平台 GRUB_CMDLINE_LINUX_DEFAULTquiet splash amd_iommuon iommupt注意我只会把参数加在GRUB_CMDLINE_LINUX_DEFAULT里因为这个变量只在默认启动项生效GRUB_CMDLINE_LINUX是“无论选哪个启动项都生效”对生产服务器来说我反而建议只用default将来若遇到内核升级启动异常可以进旧内核或者单用户模式时避开IOMMU相关的怪问题。改完保存下一步是重新生成引导配置不同发行版命令不一样发行版生成引导配置的命令Debian/Ubuntusudo update-grubRedHat/CentOS/Rocky/AlmaLinuxsudo grub2-mkconfig -o /boot/grub2/grub.cfgUEFI机器则输出到对应EFI路径Arch/Manjarosudo grub-mkconfig -o /boot/grub/grub.cfgopenSUSEsudo grub2-mkconfig -o /boot/grub2/grub.cfg执行完刷新命令后先别急着重启用/etc/default/grub里那行内容对照一下生成的grub.cfg确认参数已经带进去了grep intel_iommu\|amd_iommu\|iommupt /boot/grub/grub.cfg看到包含参数的menuentry条目后再放心reboot。4.3 另一种更快捷的改法grubby命令RedHat系如果你用的是RedHat系发行版RHEL 8/9、Rocky、Alma、CentOS Stream可以不碰/etc/default/grub直接使用grubby工具往内核启动参数里追加这个工具会顺手帮你改写后续要生成的配置sudo grubby --update-kernelALL --argsintel_iommuon iommuptgrubby的好处在多内核版本环境下特别明显它会遍历所有内核确保你下次不管选哪个旧内核参数都在。如果是Debian系没有grubby就老老实实用update-grub。4.4 重启完先别测直通花30秒验证参数重启之后第一件事不是去启动虚拟机而是检查内核启动参数有没有生效cat /proc/cmdline输出里能看到intel_iommuon iommupt就说明内核已经拿到参数了。如果看不到大概率是生成引导配置的时候没写对回上一节重新检查GRUB_CMDLINE_LINUX_DEFAULT的引号和空格别在参数里打出全角字符或者多余逗号我也干过这事。5. 确认IOMMU真的生效日志、启动参数与设备分组三重验证内核拿到参数并不代表IOMMU正在工作还需要三重验证每一步都有不同的侧重少了哪一步都可能在后续踩到隐蔽的坑。5.1 第一重dmesg日志里的关键行重启后先用命令查内核日志dmesg | grep -i -e DMAR -e IOMMUIntel平台上看到DMAR: IOMMU enabled基本可以说明IOMMU功能激活了。后面往往还会跟着DMAR-IR: Enabled IRQ remapping in x2apic mode表示中断重映射也开了这意味着CPU的中断投递也能通过IOMMU做隔离对虚拟化直通时减少中断冲突很有帮助。AMD平台上会看到AMD-Vi: IOMMU enabled以及一些关于Lazy IO/TLB flushing的日志。如果dmesg被kernel.dmesg_restrict限制导致没有输出用journalctl -k也能看到同样的启动信息journalctl -k | grep -i -e DMAR -e IOMMU5.2 第二重设备分组目录IOMMU启用后内核会在/sys/kernel/iommu_groups/下创建分组每个组代表一个IOMMU隔离域ls /sys/kernel/iommu_groups/正常启用后你会看到若干个以数字命名的目录每个目录里是一个或多个PCIe设备。这个分组信息特别关键因为它直接决定了直通能不能干净利落地做。理想情况下是一个设备一个组或者至少一个下游PCIe端口的所有设备在一个组这样可以把整组设备一起直通。如果多个互不相关的设备被塞在同一组里说明该平台IOMMU的ACSAccess Control Services能力不足直通时只能整组一起绑给虚拟机或者放弃直通。检查某个组的成员可以用ls -l /sys/kernel/iommu_groups/*/devices/ | grep -E 0000:.*输出里每个连接符指向的就是组内的物理设备因为符号链接本身带了地址一眼能看到组内到底塞了几个设备。5.3 第三重使用lspci确认设备直通准备度如果你的最终目标是直通某张显卡或网卡还需要用lspci确认设备当前挂在哪个驱动下面lspci -nnk在目标设备条目下Kernel driver in use这一行显示的是当前驱动。如果显示的是vfio-pci说明IOMMU和VFIO配合已经生效设备可以直接交给虚拟机使用。如果显示的是厂商自带驱动比如nvidia、igb、mlx5_core那你还需要做一步“夺权”操作——把设备从原驱动手上转到vfio-pci这属于直通配置的范畴下一章展开。5.4 验证失败时的排查顺序如果上面的任何一步提示IOMMU未启用按顺序排查现象可能原因/proc/cmdline无参数GRUB配置生成不正确或引导的是旧内核入口/proc/cmdline有参数但dmesg无IOMMU字样BIOS里VT-d/AMD-Vi没有开启或固件根本不支持BIOS已开但dmesg仍无平台/内核过旧部分旧内核需要额外加iommuon参数IOMMU组数量为0内核没有编译对应IOMMU驱动检查第二章节的内核配置特别想说一点如果你用的是Intel平台且BIOS里已经开了VT-d但dmesg里始终没有DMAR: IOMMU enabled可以试着补一个iommuon参数。旧版内核上intel_iommuon不一定等价于全局使能加上iommuon能更保险。新版内核不再需要这个冗余写法但多写一个并不会引起冲突。6. 开启之后的实战衔接PCIe直通、SR-IOV与性能开销IOMMU开了验证也过了但很多人到这一步还是不知道怎么把设备真正“塞”进虚拟机。这一章我把最常见的几个衔接操作串一遍同时说说开启后的性能损耗问题。6.1 把设备交给vfio-pci驱动硬件设备默认被内核自家驱动占据比如网卡被igb占着显卡被amdgpu占着。要直通给虚拟机得换成vfio-pci这个通用驱动。标准流程如下。先查到目标设备的厂商ID和设备IDlspci -nn | grep -i nvidia # 输出示例: 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA102 [GeForce RTX 3080] [10de:2206]记下10de:2206这种格式的ID对vendor:device注意这里是英伟达的10de和型号2206不是某个发行版编号然后在/etc/modprobe.d/下新建配置让vfio-pci在开机时接管这两块设备echo options vfio-pci ids10de:2206 | sudo tee /etc/modprobe.d/vfio.conf因为直通设备不允许被原驱动先绑定再移交比较好的做法是让vfio-pci在内核初始化早期就加载可以通过给GRUB加rd.driver.prevfio-pci参数实现Intel/AMD通用这个参数含义是让initramfs阶段提前加载vfio-pci模块。改完重新生成GRUB配置并重启再用lspci -nnk看设备驱动就变成vfio-pci了。如果直通的是NVIDIA显卡记得把nouveau驱动拉黑不然固件一加载就会抢占设备。写入/etc/modprobe.d/blacklist-nouveau.confblacklist nouveau然后执行sudo update-initramfs -uDebian系或sudo dracut --forceRedHat系重建initramfs。6.2 SR-IOV场景是IOMMU的另一个好搭档SR-IOV允许一张物理网卡暴露多个虚拟功能VF每个VF可以独立分配给不同的虚拟机。步骤上首先要确保物理网卡支持SR-IOV多数Intel X710/XXV710、Mellanox ConnectX系列等都支持然后使能VF# 以enp1s0f0np0网卡为例创建4个VF echo 4 | sudo tee /sys/class/net/enp1s0f0np0/device/sriov_numvfs创建完以后lspci | grep -i ethernet能看到新冒出来的VF设备接下来就是按6.1节的步骤把VF一个个绑定到vfio-pci再挂给不同虚拟机。IOMMU在这里保证了每个VF只能访问自己对应的DMA地址空间避免多个虚拟机共用一张网卡时互相越界。6.3 IOMMU的性能开销到底多大关于IOMMU开启后性能损耗的讨论网上说法差异很大。我的实测经验是在iommupt模式下普通网络包收发和NVMe存储的损耗几乎测不出来但如果不加iommupt让所有设备都走重映射高吞吐场景下确实会有个位数的性能下降延迟敏感型业务会稍微明显一点。IOMMU还会占一部分内存用来维护设备页表。数量级大概是每个IOMMU域若干MB和系统物理内存总量相比可以忽略但你在规划超售内存时还是心里有数比较好。企业级服务器上如果既想安全又想性能一般会在BIOS和内核参数里都打开IOMMU然后额外把iommupt加上再配合直通给关键业务的设备走VFIO在安全与性能之间取一个平衡点。6.4 分组太粗怎么办ACS问题的处理思路极少数平台特别是某些消费级芯片组上整个PCIe下游口的所有设备都堆在同一个IOMMU group里想直通其中一个设备就只能整组搬走非常憋屈。这是因为PCIe交换机端口没有完整实现ACS能力。上世纪的技术传统里这不算事但在虚拟化时代就成大麻烦了。社区流传的解法是给内核打ACS override补丁强制内核忽略设备的ACS能力、按更细的粒度分组。这个做法成功率不低但毕竟是“遵医嘱”之外的灰色操作我自己的态度是个人折腾、测试环境可以试试生产环境就算了——IOMMU本来就是安全隔离机制你强行绕过它的分组原则等于把安全门又开了一条缝。真想做得干净老老实实选服务器平台或者芯片组支持完整ACS的主板比折腾补丁省心太多。7. 最后补几个我踩过的坑折腾IOMMU这两年有几次排查把我绕得够呛在这顺手记下来应该能帮你避掉几个常见的雷。第一个坑是IBM/联想服务器和某些工作站主板的BIOS默认策略。它们把VT-d的默认值设成了Disabled同时系统里跑着Windows时不会有什么明显问题但一旦换到Linux做KVM直通就各种诡异报错。所以装了新机器第一件事就是进BIOS把Virtualization相关的所有项目全部打开别只盯着CPU虚拟化那一项。第二个坑是UEFI安全启动Secure Boot。有些主板开了Secure Boot后Linux内核引导参数会被额外校验虽然参数本身不影响签名但部分OEM固件会在启用VT-d同时开启某种DMA保护策略结果你加了intel_iommuon后反而连引导都进不去。解决办法是尝试更新固件版本或者暂时关闭Secure Boot测试前提是你的发行版在Secure Boot关闭下能正常跑绝大多数桌面场景没问题重开之前确认生产环境策略。第三个坑是内核模块的加载顺序。有次我在一台AMD机器上开了IOMMUamd_iommuon也加了dmesg里明确能看到AMD-Vi: IOMMU enabled但VFIO绑定网卡后虚拟机一启动就挂。最后发现是igb驱动在initramfs阶段就把网卡顺手接管了vfio-pci等设备被推出多了才加载根本没有机会抢占。给GRUB加rd.driver.prevfio-pci同时把igb加入模块黑名单initramfs重建一遍问题才彻底消失。这个坑提醒我验证IOMMU只是第一步后面驱动抢设备还有一堆细节等着你。从BIOS到内核参数再到验证和实战衔接IOMMU开启本身半个小时就能搞定但真正搞清楚它每层的作用边界才能在直通、安全、性能这三者之间找到合适的平衡点。我是建议按“BIOS检查 → 内核参数 → dmesg验证 → 设备分组确认”这个顺序一步步来把日志里每一行关键输出都看明白再往下走后面直通设备的返工率会低得多。
返回列表