最近折腾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 检查CPU:Intel VT-d还是AMD-Vi
Intel平台上的IOMMU实现叫VT-d(Virtualization Technology for Directed I/O),AMD平台的叫AMD-Vi,有时候也直接叫IOMMU。不管是哪家,几乎所有2015年之后的服务器/桌面CPU都支持,但低端切割产品线里偶尔会有例外。
CPU支持情况可以通过lscpu和/proc/cpuinfo检查虚拟化指令集:
grep -E -o '(vmx|svm)' /proc/cpuinfo | sort -u如果输出里同时包含vmx(Intel)或者svm(AMD),说明CPU虚拟化基础特性在,但这只是支持VT-x/AMD-V(CPU虚拟化)的标识,和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-d,AMD平台搜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_IOMMU=y或者CONFIG_AMD_IOMMU=y,内核这关就算过了。要是显示# CONFIG_INTEL_IOMMU is not set,那就得换个发行版内核或者重新编译内核,这不是改个参数能解决的。
三关都过,才到了真正动手的时间点。
3. 固件层面打开通道:BIOS/UEFI里如何设置VT-d与AMD-Vi
3.1 VT-d和VT-x的区别别搞混
有些朋友在BIOS里只开了Intel Virtualization Technology(VT-x),就跑回来问我为什么intel_iommu=on不起作用。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 Bridge | Intel VT-d / AMD IOMMU |
| 技嘉消费级 | Settings → Miscellaneous / IO Port | VT-d / AMD CBS → NBIO → IOMMU |
| 微星消费级 | Overclocking → CPU Features / Settings → Advanced | Intel VT-d / AMD IOMMU |
| 超微服务器 | Advanced → PCIe/PCI/PnP Configuration | VT-d / AMD IOMMU |
| Dell服务器 | System BIOS → Integrated Devices | VT-d / IOMMU Support |
| HPE服务器 | Advanced → PCI Device Options | Intel 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_iommu=on - AMD平台:
amd_iommu=on - 通用参数:
iommu=pt
我在实际使用中还会再加上iommu=pt。pt是pass-through的缩写,意思是“对于不做直通的设备,直接绕过IOMMU重映射”。这个参数需要解释一下:IOMMU启用后,默认情况下所有PCIe设备的DMA操作都要经过页表翻译,这会有少量性能开销。对网络吞吐、NVMe读写这种数据面操作来说,哪怕几个百分点的性能损失也不划算。加iommu=pt后,只有你明确绑定给VFIO的设备走IOMMU,其余设备保持直通模式,性能几乎无损。Intel平台官方文档里iommu=pt也是推荐项,AMD平台同样适用。
另外还有一个可选项intel_iommu=on,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_DEFAULT="quiet splash intel_iommu=on iommu=pt" # AMD平台 GRUB_CMDLINE_LINUX_DEFAULT="quiet splash amd_iommu=on iommu=pt"注意,我只会把参数加在GRUB_CMDLINE_LINUX_DEFAULT里,因为这个变量只在默认启动项生效;GRUB_CMDLINE_LINUX是“无论选哪个启动项都生效”,对生产服务器来说,我反而建议只用default,将来若遇到内核升级启动异常,可以进旧内核或者单用户模式时避开IOMMU相关的怪问题。
改完保存,下一步是重新生成引导配置,不同发行版命令不一样:
| 发行版 | 生成引导配置的命令 |
|---|---|
| Debian/Ubuntu | sudo update-grub |
| RedHat/CentOS/Rocky/AlmaLinux | sudo grub2-mkconfig -o /boot/grub2/grub.cfg(UEFI机器则输出到对应EFI路径) |
| Arch/Manjaro | sudo grub-mkconfig -o /boot/grub/grub.cfg |
| openSUSE | sudo grub2-mkconfig -o /boot/grub2/grub.cfg |
执行完刷新命令后,先别急着重启,用/etc/default/grub里那行内容对照一下生成的grub.cfg,确认参数已经带进去了:
grep 'intel_iommu\|amd_iommu\|iommu=pt' /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-kernel=ALL --args="intel_iommu=on iommu=pt"grubby的好处在多内核版本环境下特别明显,它会遍历所有内核,确保你下次不管选哪个旧内核,参数都在。如果是Debian系,没有grubby,就老老实实用update-grub。
4.4 重启完先别测直通,花30秒验证参数
重启之后,第一件事不是去启动虚拟机,而是检查内核启动参数有没有生效:
cat /proc/cmdline输出里能看到intel_iommu=on iommu=pt就说明内核已经拿到参数了。如果看不到,大概率是生成引导配置的时候没写对,回上一节重新检查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的ACS(Access 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仍无 | 平台/内核过旧,部分旧内核需要额外加iommu=on参数 |
| IOMMU组数量为0 | 内核没有编译对应IOMMU驱动,检查第二章节的内核配置 |
特别想说一点:如果你用的是Intel平台且BIOS里已经开了VT-d,但dmesg里始终没有DMAR: IOMMU enabled,可以试着补一个iommu=on参数。旧版内核上intel_iommu=on不一定等价于全局使能,加上iommu=on能更保险。新版内核不再需要这个冗余写法,但多写一个并不会引起冲突。
6. 开启之后的实战衔接:PCIe直通、SR-IOV与性能开销
IOMMU开了,验证也过了,但很多人到这一步还是不知道怎么把设备真正“塞”进虚拟机。这一章我把最常见的几个衔接操作串一遍,同时说说开启后的性能损耗问题。
6.1 把设备交给vfio-pci驱动
硬件设备默认被内核自家驱动占据,比如网卡被igb占着,显卡被amdgpu占着。要直通给虚拟机,得换成vfio-pci这个通用驱动。标准流程如下。
先查到目标设备的厂商ID和设备ID:
lspci -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 ids=10de:2206" | sudo tee /etc/modprobe.d/vfio.conf因为直通设备不允许被原驱动先绑定再移交,比较好的做法是让vfio-pci在内核初始化早期就加载,可以通过给GRUB加rd.driver.pre=vfio-pci参数实现(Intel/AMD通用,这个参数含义是让initramfs阶段提前加载vfio-pci模块)。改完重新生成GRUB配置并重启,再用lspci -nnk看,设备驱动就变成vfio-pci了。
如果直通的是NVIDIA显卡,记得把
nouveau驱动拉黑,不然固件一加载就会抢占设备。写入/etc/modprobe.d/blacklist-nouveau.conf:blacklist nouveau,然后执行sudo update-initramfs -u(Debian系)或sudo dracut --force(RedHat系)重建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开启后性能损耗的讨论,网上说法差异很大。我的实测经验是:在iommu=pt模式下,普通网络包收发和NVMe存储的损耗几乎测不出来;但如果不加iommu=pt,让所有设备都走重映射,高吞吐场景下确实会有个位数的性能下降,延迟敏感型业务会稍微明显一点。
IOMMU还会占一部分内存用来维护设备页表。数量级大概是每个IOMMU域若干MB,和系统物理内存总量相比可以忽略,但你在规划超售内存时还是心里有数比较好。企业级服务器上,如果既想安全又想性能,一般会在BIOS和内核参数里都打开IOMMU,然后额外把iommu=pt加上,再配合直通给关键业务的设备走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_iommu=on后反而连引导都进不去。解决办法是尝试更新固件版本,或者暂时关闭Secure Boot测试(前提是你的发行版在Secure Boot关闭下能正常跑,绝大多数桌面场景没问题,重开之前确认生产环境策略)。
第三个坑是内核模块的加载顺序。有次我在一台AMD机器上开了IOMMU,amd_iommu=on也加了,dmesg里明确能看到AMD-Vi: IOMMU enabled,但VFIO绑定网卡后虚拟机一启动就挂。最后发现是igb驱动在initramfs阶段就把网卡顺手接管了,vfio-pci等设备被推出多了才加载,根本没有机会抢占。给GRUB加rd.driver.pre=vfio-pci,同时把igb加入模块黑名单,initramfs重建一遍,问题才彻底消失。这个坑提醒我,验证IOMMU只是第一步,后面驱动抢设备还有一堆细节等着你。
从BIOS到内核参数,再到验证和实战衔接,IOMMU开启本身半个小时就能搞定,但真正搞清楚它每层的作用边界,才能在直通、安全、性能这三者之间找到合适的平衡点。我是建议按“BIOS检查 → 内核参数 → dmesg验证 → 设备分组确认”这个顺序一步步来,把日志里每一行关键输出都看明白再往下走,后面直通设备的返工率会低得多。