KVM PCIe 直通与 SR-IOV:GPU 透传、网卡虚拟化实战
详解 KVM 虚拟化中的 PCIe 设备直通(Passthrough)和 SR-IOV(单根 IO 虚拟化)技术:IOMMU 启用、VFIO 驱动绑定、GPU 完整透传给虚拟机、SR-IOV 网卡创建 VF 并分配给多个 VM,覆盖 NVIDIA GPU 透传、Intel/Mellanox 网卡 SR-IOV 配置全流程。
PCIe 直通(Passthrough)和 SR-IOV 是虚拟化中实现接近裸机性能的两种技术。PCIe 直通把整块物理设备(如 GPU、NVMe SSD)独占分配给一台 VM;SR-IOV 把一块物理网卡虚拟成多个“虚拟函数(VF)“分别分配给多台 VM。两者都绕过了 QEMU 的软件模拟层,性能接近物理机。
两种技术对比
PCIe 直通(Passthrough):
物理设备 → 整体分配给一台 VM
VM 独占该设备(其他 VM 无法使用)
性能:= 物理机(完全无虚拟化开销)
适用:GPU 训练、NVMe SSD、高性能加密卡
SR-IOV(Single Root IO Virtualization):
物理设备 → 创建多个 VF(虚拟函数)→ 每个 VM 一个 VF
多个 VM 共享同一物理设备
性能:接近物理机(VF 之间硬件隔离)
适用:高性能网卡(Mellanox/Intel ConnectX)、NVMe-oF
前提条件(两者相同):
1. CPU 支持 VT-d(Intel)或 AMD-Vi
2. BIOS/UEFI 开启 IOMMU
3. 内核加载 VFIO 驱动
一、启用 IOMMU
BIOS 设置
进入 BIOS → Advanced → Virtualization
Intel VT-d:Enable
SR-IOV Support:Enable(SR-IOV 需要)
IOMMU:Enable(某些主板需要单独开)
内核参数(GRUB)
# 查看当前 GRUB 配置
cat /etc/default/grub | grep CMDLINE
# Intel CPU
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"
# AMD CPU
GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on iommu=pt"
# 应用并重启
update-grub
reboot
# 验证 IOMMU 已启用
dmesg | grep -E "IOMMU|DMAR|AMD-Vi"
# [ 0.123] DMAR: IOMMU enabled
# [ 0.456] iommu: Default domain type: Passthrough (set via kernel command line)
加载 VFIO 内核模块
# 加载模块
modprobe vfio vfio-pci vfio_iommu_type1
# 开机自动加载
cat >> /etc/modules << 'EOF'
vfio
vfio-pci
vfio_iommu_type1
EOF
# 验证
lsmod | grep vfio
# vfio_pci 32768 0
# vfio_iommu_type1 28672 0
# vfio 36864 2 vfio_pci,vfio_iommu_type1
二、GPU 完整透传
找到 GPU 的 PCI 地址
# 列出所有 PCI 设备
lspci | grep -E "VGA|NVIDIA|AMD/ATI"
# 00:02.0 VGA compatible controller: Intel Corporation ... ← 集成显卡(宿主机用)
# 01:00.0 VGA compatible controller: NVIDIA Corporation A100 (rev a1)
# 01:00.1 Audio device: NVIDIA Corporation GA100 High Definition Audio
# 查看 IOMMU 分组(同一组的设备需要一起透传)
for d in /sys/kernel/iommu_groups/*/devices/*; do
n=${d#*/iommu_groups/*}; n=${n%%/*}
printf 'IOMMU Group %s ' "$n"
lspci -nns "${d##*/}"
done | sort -n -k3
# IOMMU Group 1:
# 01:00.0 VGA: NVIDIA A100 [10de:20b0]
# 01:00.1 Audio: NVIDIA [10de:228b]
# 同组的 VGA 和 Audio 都要一起透传
解绑 GPU(从 NVIDIA 驱动解绑,绑定到 VFIO)
# 方法1:黑名单 NVIDIA 驱动(开机不加载)
cat > /etc/modprobe.d/blacklist-nvidia.conf << 'EOF'
blacklist nouveau
blacklist nvidia
blacklist nvidia_drm
blacklist nvidia_modeset
EOF
# 设置 VFIO 驱动绑定(针对特定设备 ID)
# 从 lspci -n 获取 Vendor:Device ID
lspci -n | grep "01:00"
# 01:00.0 0300: 10de:20b0 ← NVIDIA A100
# 01:00.1 0403: 10de:228b ← NVIDIA Audio
cat > /etc/modprobe.d/vfio.conf << 'EOF'
options vfio-pci ids=10de:20b0,10de:228b
EOF
# 更新 initramfs 并重启
update-initramfs -u -k all
reboot
# 验证 GPU 已绑定到 VFIO
lspci -v | grep -A 5 "01:00.0"
# 01:00.0 VGA compatible controller: NVIDIA A100
# Kernel driver in use: vfio-pci ← 已绑定 VFIO
添加 GPU 到虚拟机
<!-- virsh edit 添加 GPU 直通 -->
<domain>
<devices>
<!-- GPU 本体 -->
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</source>
<address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
</hostdev>
<!-- GPU 音频(需要和 GPU 一起透传)-->
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x1'/>
</source>
<address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x1'/>
</hostdev>
<!-- 去除虚拟显卡(GPU 直通后不需要 VGA 模拟) -->
<!-- 注意:同时去掉 video 设备和 graphics 设备 -->
</devices>
<!-- CPU 设置为 host-passthrough(GPU 驱动可能需要 CPUID) -->
<cpu mode='host-passthrough' check='none'>
<topology sockets='1' dies='1' cores='8' threads='2'/>
</cpu>
</domain>
# 在 VM 内验证 GPU 可见
virsh start myvm
virsh console myvm
# VM 内执行:
lspci | grep NVIDIA
# 06:00.0 VGA compatible controller: NVIDIA A100
# 安装 NVIDIA 驱动(在 VM 内)
apt install nvidia-driver-535
nvidia-smi
# +-----------------------------------------------------------------------------+
# | NVIDIA-SMI 535.129.03 Driver Version: 535.129.03 CUDA Version: 12.2 |
# |-------------------------------+----------------------+----------------------+
# | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC|
# | 0 A100 Off | 00000000:06:00.0 Off | 0 |
三、SR-IOV 网卡虚拟化
SR-IOV 把一块物理网卡(PF,Physical Function)分成多个虚拟网卡(VF,Virtual Function),每个 VM 获得一个 VF,享有独立的 MAC 地址和 VLAN,网络性能接近物理机。
Intel X710/X550 SR-IOV 配置
# 查看网卡是否支持 SR-IOV
lspci | grep "Ethernet"
# 02:00.0 Ethernet controller: Intel Corporation Ethernet Controller X710 for 10GbE SFP+ (rev 02)
ethtool -i ens3f0 | grep driver
# driver: i40e ← Intel 10GbE 驱动,支持 SR-IOV
# 查看最大 VF 数量
cat /sys/class/net/ens3f0/device/sriov_totalvfs
# 64 ← 最多 64 个 VF
# 创建 VF(创建 4 个虚拟函数)
echo 4 > /sys/class/net/ens3f0/device/sriov_numvfs
# 验证 VF 已创建
lspci | grep "Virtual Function"
# 02:02.0 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series
# 02:02.1 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series
# 02:02.2 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series
# 02:02.3 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series
# 永久生效(写入 udev 规则)
cat > /etc/udev/rules.d/99-sriov.rules << 'EOF'
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="aa:bb:cc:dd:ee:ff", ATTR{device/sriov_numvfs}="4"
EOF
# 或者写 systemd service
cat > /etc/systemd/system/sriov.service << 'EOF'
[Unit]
Description=Enable SR-IOV
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo 4 > /sys/class/net/ens3f0/device/sriov_numvfs'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now sriov
配置 VF MAC 地址和 VLAN
# 给 VF 设置 MAC 地址(避免冲突)
ip link set ens3f0 vf 0 mac 00:11:22:33:44:01
ip link set ens3f0 vf 1 mac 00:11:22:33:44:02
ip link set ens3f0 vf 2 mac 00:11:22:33:44:03
ip link set ens3f0 vf 3 mac 00:11:22:33:44:04
# 给 VF 设置 VLAN(租户隔离)
ip link set ens3f0 vf 0 vlan 100 # VM 1 在 VLAN 100
ip link set ens3f0 vf 1 vlan 200 # VM 2 在 VLAN 200
# 限制 VF 带宽
ip link set ens3f0 vf 0 max_tx_rate 1000 # 限速 1000 Mbps
# 查看 VF 状态
ip link show ens3f0
# 2: ens3f0: <BROADCAST,MULTICAST,UP,LOWER_UP>
# vf 0 link/ether 00:11:22:33:44:01 brd ff:ff:ff:ff:ff:ff, vlan 100
# vf 1 link/ether 00:11:22:33:44:02 brd ff:ff:ff:ff:ff:ff, vlan 200
分配 VF 给虚拟机
<!-- virsh edit:添加 SR-IOV VF -->
<devices>
<!-- 分配 VF 0(02:02.0)给此 VM -->
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x02' slot='0x02' function='0x0'/>
</source>
</hostdev>
</devices>
# 在 VM 内验证
lspci | grep Ethernet
# 00:06.0 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series
# 速度测试(iperf3)
iperf3 -c 192.168.1.200 -t 30
# [ ID] Interval Transfer Bandwidth
# [ 5] 0.0-30.0s 33.7 GBytes 9.64 Gbits/sec ← 接近 10G 物理网卡性能
Proxmox VE 中的 PCIe 直通
# Proxmox 简化了直通配置:
# 1. 确认 IOMMU 已启用
dmesg | grep -e DMAR -e IOMMU | grep -i "enabled"
# 2. 在 Proxmox 中添加直通设备
# Web UI:VM → Hardware → Add → PCI Device
# 勾选 "All Functions"(同时直通所有功能:显卡+音频)
# 勾选 "ROM-Bar"(某些 GPU 需要)
# 勾选 "PCI-Express"(PCIe 性能模式)
# 命令行等效:
qm set 101 -hostpci0 01:00,allFunctions=1,pcie=1
# 对于 NVIDIA GPU,还需要隐藏虚拟化标志(否则驱动报错 43)
qm set 101 -cpu host,hidden=1,flags=+pcid
# 并在 VM 的配置中添加:
echo "args: -cpu 'host,+kvm_pv_unhalt,+kvm_pv_eoi,hv_vendor_id=proxmox,kvm=off'" >> /etc/pve/qemu-server/101.conf
故障排查
# 常见错误:IOMMU 分组包含其他设备
# Error: failed to add PCI group, device not isolated
# 解法:检查 IOMMU 分组,同组设备全部一起透传
for d in /sys/kernel/iommu_groups/1/devices/*; do
lspci -nns "${d##*/}"
done
# 常见错误:VFIO 驱动绑定失败
# Error: bind /sys/bus/pci/devices/0000:01:00.0/driver_override: permission denied
# 解法:先解绑原驱动
echo "0000:01:00.0" > /sys/bus/pci/drivers/nvidia/unbind
echo "vfio-pci" > /sys/bus/pci/devices/0000:01:00.0/driver_override
echo "0000:01:00.0" > /sys/bus/pci/drivers/vfio-pci/bind
# SR-IOV VF 数量回 0(重启后失效)
# 确认通过 systemd service 或 udev 持久化
小结
PCIe 直通和 SR-IOV 的核心流程都是:启用 IOMMU → 解绑原驱动 → 绑定 VFIO → 分配给 VM。GPU 直通适合需要 GPU 独占算力的场景(AI 训练、图形渲染),整块 GPU 给一台 VM 独用。SR-IOV 适合网络密集型场景,一块物理网卡分给 4-64 台 VM 共用,每台 VM 的网络性能接近裸机。Proxmox VE 用户通过 Web UI 的 PCI Device 选项可以简化直通配置,不需要手动编辑 XML。
