资讯动态

KVM PCI Passthrough实战:IOMMU分组、VFIO绑定与性能调优

发布时间:2026/10/15 4:39:54 来源:尧图企业网站定制
1. 从一个真实需求说起为什么要在KVM里做PCI Passthrough很多人在用KVM做虚拟化的时候最初都是靠virtio网卡跑网络。virtio确实好用半虚拟化驱动性能不差CPU占用也低日常业务完全够用。但总有一些场景virtio满足不了。比如你要在虚拟机里跑一个对网络延迟极其敏感的应用或者你需要虚拟机直接接管一块物理网卡来做SR-IOV的PF管理又或者你要在虚拟机里做DPDK收包、跑P4交换机、搞网络功能虚拟化实验。这些场景下virtio那层软件模拟就成了瓶颈你需要的是让虚拟机直接“看见”并“拥有”一块真实的物理PCI设备。这就是PCI Passthrough要解决的问题。它的核心思路很直接把宿主机上的一块物理PCI设备从宿主机手里“拿走”交给虚拟机独占使用。虚拟机里的操作系统看到的是一块真实的硬件而不是一个模拟出来的设备。网卡、GPU、NVMe盘、FPGA加速卡都可以这么干。网络场景下我们通常叫它“网卡直通”。我最早接触这个技术是因为要在虚拟机里跑一个多队列收包的压力测试virtio的多队列虽然支持但中断亲和性和队列绑定总感觉隔了一层数据路径上多了一次拷贝和一次上下文切换测出来的数字不够“裸”。换成PCI Passthrough之后虚拟机里的网卡就是物理网卡本身收包路径和物理机上跑几乎没有区别测试结果才有说服力。这篇文章我会把KVM下PCI Passthrough的完整链路讲清楚从硬件前提、内核参数、IOMMU分组到VFIO绑定、虚拟机XML配置再到实际跑起来之后可能踩的坑。内容偏实操但每个关键步骤我都会解释“为什么这么做”这样你遇到变体场景时能自己判断而不是照抄命令。注意PCI Passthrough的前提是CPU和主板支持IOMMUIntel叫VT-dAMD叫AMD-Vi并且BIOS里要打开。没有IOMMU设备直通要么根本起不来要么起得来但DMA乱飞直接把宿主机内存写坏。这是硬门槛绕不过去。2. IOMMU到底在保护什么直通的安全底座2.1 没有IOMMU的世界DMA是一把没有鞘的刀要理解PCI Passthrough先得理解DMA。物理设备访问内存靠的是DMA直接内存访问。设备拿到的是物理地址它往这个地址写数据CPU完全不参与。这在物理机时代没问题因为整台机器的内存都归这台机器管。但在虚拟化环境里如果你把一块物理网卡直接给虚拟机用这块网卡发出的DMA请求用的是它自己看到的地址而虚拟机里的“物理地址”其实是宿主机上的虚拟地址经过一层映射后的结果。如果中间没有隔离机制网卡一写就可能写到宿主机内核或者其他虚拟机的内存里去。IOMMU就是这层隔离机制。它相当于给DMA加了一个地址翻译表设备发出的地址先经过IOMMU翻译成真正的物理地址翻译表由宿主机内核维护。这样虚拟机里的设备只能访问分配给它的那段内存越界直接失败。同时IOMMU还负责中断重映射保证设备中断能正确投递到对应的虚拟机。所以IOMMU有两个核心作用一是DMA重映射保证内存隔离安全二是中断重映射保证中断路由正确。少了任何一个直通都不完整。2.2 IOMMU分组为什么有时候一块网卡直通不了IOMMU不是按单个设备分组的而是按“IOMMU Group”分组。一个Group里的设备共享同一个IOMMU翻译表也就是说同一个Group里的设备要么一起直通要么都不直通不能拆开。为什么会有Group这个概念因为PCIe拓扑里很多设备挂在同一个PCIe Switch或者同一个Root Complex端口下它们之间的隔离粒度受硬件限制。如果两个设备共享一个翻译表你把其中一个直通给虚拟机另一个留在宿主机那宿主机就没法保证这两个设备之间的DMA隔离了。所以内核干脆把整个Group作为一个最小直通单位。实际排查时用下面这个命令看Group划分for d in /sys/kernel/iommu_groups/*/devices/*; do n${d#*/iommu_groups/*}; n${n%%/*} printf IOMMU Group %s $n lspci -nns ${d##*/} done输出里每个Group下面挂了哪些设备一目了然。如果你要直通的网卡和别的设备分在同一个Group而那个设备你不想直通那就麻烦了。常见的情况是网卡和它的PCIe桥在同一个Group这时候通常可以把桥也一起直通或者换一个PCIe插槽让网卡独占一个Root Complex端口。我遇到过一块双口网卡两个口在同一个Group里我只想直通其中一个口。这种情况没法拆只能两个口一起直通给同一台虚拟机或者干脆换一张单口卡。这是硬件拓扑决定的软件层面无解。2.3 开启IOMMU内核参数与验证确认硬件支持之后要在宿主机内核启动参数里打开IOMMU。Intel平台加intel_iommuon iommuptAMD平台加amd_iommuon iommuptiommupt的意思是“passthrough模式”只对需要直通的设备启用IOMMU翻译其他设备走恒等映射减少性能开销。这个参数在直通场景下基本是标配。改完参数重启然后验证dmesg | grep -i iommu看到“DMAR: IOMMU enabled”之类的字样就说明开了。再看/sys/kernel/iommu_groups/目录下有没有内容有内容说明Group已经划分出来了。提示有些主板BIOS里VT-d和VT-x是两个独立开关别只开了虚拟化忘了开IOMMU。另外某些消费级主板虽然CPU支持VT-d但BIOS里没有开放选项这种就只能换平台了。3. VFIO把设备从宿主机手里“接管”过来3.1 为什么不能直接用pci-stub早期做直通大家用的是pci-stub驱动。它的作用是把设备从原驱动上解绑让设备处于“无人认领”的状态然后虚拟机才能接管。但pci-stub有个问题它只解决了“解绑”没解决“安全接管”。设备虽然没驱动了但它的DMA能力还在如果虚拟机还没起来设备乱发DMA照样能写坏内存。VFIOVirtual Function I/O是更现代的方案。它是一套完整的用户态设备访问框架核心是把设备的所有资源——MMIO区域、DMA能力、中断——都纳入一个受控的容器里。虚拟机通过VFIO拿到设备所有DMA都经过IOMMU翻译中断也经过重映射。安全性和完整性都比pci-stub好得多。现在做PCI Passthrough标准做法就是VFIO。pci-stub基本已经退出历史舞台了。3.2 绑定VFIO的两种方式第一种是启动时绑定。在内核参数里加vfio-pci.ids8086:10fb,8086:1521这里的8086:10fb是设备的vendor ID和device ID用lspci -nn可以查到。这种方式适合宿主机上固定要直通的设备开机就绑定好干净利落。第二种是运行时绑定。先解绑原驱动再绑定vfio-pci# 查看设备当前驱动 lspci -k -s 03:00.0 # 解绑原驱动 echo 0000:03:00.0 /sys/bus/pci/devices/0000:03:00.0/driver/unbind # 绑定vfio-pci echo 8086 10fb /sys/bus/pci/drivers/vfio-pci/new_id运行时绑定适合临时测试但重启就失效了。生产环境建议用启动参数或者udev规则固化下来。绑定成功后用lspci -k看设备驱动那一栏应该显示vfio-pci。同时/dev/vfio/目录下会出现对应的Group文件比如/dev/vfio/15这个数字就是IOMMU Group号。3.3 一个容易忽略的细节设备功能Function的拆分一块PCIe设备可能有多个Function比如一个网卡有PF物理功能和若干VF虚拟功能或者一个多功能设备有网络功能和存储功能。直通的时候你要明确直通的是哪个Function。用lspci看的时候03:00.0和03:00.1就是同一个物理设备上的两个Function。如果你只直通.0那.1还在宿主机上。但如果它们在同一个IOMMU Group里你就得一起处理。通常的做法是要么把整个设备的所有Function都直通给同一台虚拟机要么确认它们分在不同的Group里可以独立操作。我踩过一次坑直通了一块网卡的一个口结果另一个口在宿主机上莫名其妙down了。查了半天发现两个口共享同一个IOMMU Group直通一个口的时候另一个口的DMA也被纳入了虚拟机的翻译表宿主机驱动访问它就出错了。后来把两个口都直通给虚拟机才正常。4. 虚拟机配置从XML到实际启动4.1 用hostdev挂载PCI设备libvirt里挂载PCI设备用hostdev标签模式选subsystempci。一个典型的配置长这样hostdev modesubsystem typepci managedyes source address domain0x0000 bus0x03 slot0x00 function0x0/ /source /hostdevmanagedyes的意思是让libvirt自动帮你处理设备解绑和VFIO绑定你不需要手动去操作sysfs。这个在大多数场景下都推荐开着省事。managedno则需要你自己提前把设备绑定到vfio-pcilibvirt只负责把它传给虚拟机。address里的四个字段对应lspci看到的地址domain通常是0000bus、slot、function就是03:00.0拆开的结果。4.2 中断亲和性与CPU绑定直通设备的中断默认会投递到宿主机上某个CPU然后由VFIO转发给虚拟机。如果这个CPU正好在忙别的中断延迟就会上去。对于网络直通这种对延迟敏感的场景建议把虚拟机vCPU和直通设备的中断都绑定到同一批物理CPU上减少跨核通信。可以在虚拟机XML里配置cputune做vCPU绑定cputune vcpupin vcpu0 cpuset4/ vcpupin vcpu1 cpuset5/ vcpupin vcpu2 cpuset6/ vcpupin vcpu3 cpuset7/ /cputune然后在宿主机上找到直通设备的中断号把中断亲和性也设到这几个核上# 查看中断号 grep vfio /proc/interrupts # 设置亲和性比如中断号是120 echo 4-7 /proc/irq/120/smp_affinity_list这样中断处理和数据收发的vCPU在同一个NUMA节点甚至同一批核上延迟会明显改善。4.3 大页内存与NUMA对齐网络直通场景下虚拟机收包用的内存如果走普通4K页TLB miss会比较多。配上大页内存HugePages能减少地址翻译开销。在XML里加memoryBacking hugepages/ /memoryBacking同时在宿主机上预留大页echo 4096 /proc/sys/vm/nr_hugepages4096个2M大页就是8G内存根据虚拟机内存大小调整。NUMA对齐也很重要。如果宿主机是多路CPU直通设备挂在哪个NUMA节点虚拟机的内存和vCPU就尽量绑到同一个节点。用lspci -vv看设备的NUMA节点用numactl -H看节点拓扑然后在XML里用numatune做绑定。跨NUMA访问的延迟比同NUMA高不少网络直通这种场景下能感觉到差异。5. 实测中那些文档不会告诉你的坑5.1 直通后宿主机网络断了这是最常见的问题。你把一块网卡直通给虚拟机宿主机上如果还有别的服务在用这块网卡直通的一瞬间宿主机网络就断了。更隐蔽的情况是宿主机上有多个网卡做bond你直通了其中一个bond就残了。解决办法很简单直通的网卡不要用于宿主机管理网络。宿主机管理口用另一块网卡或者用板载网卡。如果非要用同一块那就得在直通之前把宿主机的网络配置从这块卡上摘干净。还有一种情况是直通网卡和宿主机管理网卡在同一个IOMMU Group直通的时候把管理网卡也带走了。这个在规划阶段就要用IOMMU Group命令查清楚别等配好了才发现。5.2 虚拟机里网卡识别不到或者驱动不对直通过去的网卡虚拟机里看到的是真实硬件。如果虚拟机内核里没有对应驱动网卡就起不来。比如某些较新的25G/100G网卡老版本内核可能没有驱动。这时候要么升级虚拟机内核要么换一张兼容性好的网卡。另外有些网卡直通后需要在虚拟机里重新刷固件或者配置因为宿主机上的配置不会跟着过去。比如网卡的VLAN配置、流控配置直通后都是默认状态需要在虚拟机里重新设。5.3 热插拔直通设备导致虚拟机崩溃libvirt支持热插拔hostdev设备但PCI设备的热插拔在虚拟机里支持得并不好。尤其是Windows虚拟机热插拔PCI设备经常蓝屏。Linux虚拟机相对好一些但也不是所有内核版本都稳。我的建议是直通设备在虚拟机启动时就配好不要热插拔。如果非要热插拔先在测试环境验证虚拟机内核和驱动是否支持别在生产上直接试。5.4 性能没有预期高直通之后发现性能提升不明显甚至还不如virtio这种情况也有。原因通常有几个一是中断亲和性没配好中断在核间乱跳二是内存没走大页TLB开销大三是NUMA没对齐跨节点访问四是网卡本身的多队列没配好虚拟机里只用了单队列。排查的时候先用ethtool -S看虚拟机里网卡的队列收包分布如果只有一个队列在收那多队列就没生效。再看/proc/interrupts里中断分布如果都集中在一个核上亲和性就没配对。逐项排查通常能定位到瓶颈。5.5 迁移与快照的限制直通设备绑定了具体的物理硬件所以带直通设备的虚拟机不能做在线迁移。这是硬限制因为目标宿主机上不一定有同样的设备。快照也一样直通设备的状态没法保存到快照里。如果业务需要迁移就不能用直通得回到virtio或者SR-IOV方案。SR-IOV算是直通和virtio之间的折中PF留在宿主机VF直通给虚拟机。VF可以迁移因为VF是虚拟出来的目标宿主机上只要PF在就能创建对应的VF。但SR-IOV的配置比纯直通复杂需要网卡和驱动都支持。6. 收尾几个我反复用到的检查命令每次配直通我都会按这个顺序过一遍基本能覆盖九成以上的问题# 1. 确认IOMMU开启 dmesg | grep -i iommu # 2. 查看IOMMU Group划分 for d in /sys/kernel/iommu_groups/*/devices/*; do n${d#*/iommu_groups/*}; n${n%%/*} printf Group %s: $n; lspci -nns ${d##*/} done # 3. 确认设备绑定到vfio-pci lspci -k -s 03:00.0 # 4. 确认vfio设备节点存在 ls -l /dev/vfio/ # 5. 虚拟机启动后确认设备在guest里 # 在虚拟机内执行 lspci -nn这套流程走下来大部分直通问题都能定位。PCI Passthrough这个技术本身不复杂复杂的是硬件拓扑的差异和内核版本的差异。同一个操作在不同主板上可能表现完全不一样。所以我的经验是先查IOMMU Group再动手配置别上来就改XML。Group划分决定了你能做什么不能做什么这是所有后续操作的基础。另外直通设备一旦交给虚拟机宿主机就彻底失去对它的控制了。调试的时候如果虚拟机起不来设备又绑在vfio-pci上宿主机也没法用。这时候要么把虚拟机配置改回去重启要么手动把设备从vfio-pci解绑重新绑回原驱动。留一条后路别把自己锁在外面。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑