技术博客

KVM PCIe 直通与 SR-IOV:GPU 透传、网卡虚拟化实战

详解 KVM 虚拟化中的 PCIe 设备直通(Passthrough)和 SR-IOV(单根 IO 虚拟化)技术:IOMMU 启用、VFIO 驱动绑定、GPU 完整透传给虚拟机、SR-IOV 网卡创建 VF 并分配给多个 VM,覆盖 NVIDIA GPU 透传、Intel/Mellanox 网卡 SR-IOV 配置全流程。

KVMPCIe直通SR-IOVGPUVFIOIOMMU虚拟化

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。