在QEMU中运行GPU passthrough的Debian

备注

本文实践是在 BLFS QEMU 基础上完成:

GPU passthrough 是在虚拟机中允许Linux内核直接访问Host主机的PCI GPU技术。此时GPU设备就好像直接由VM驱动,而VM将PCI设备视为它自己的物理连接模式。GPU passthrough 也称为 IOMMU ,虽然并不完全正确,因为IOMMU实际上是一种硬件技术,不仅能够提供GPU passthrough,而且还能提供其他功能,例如对DMA攻击的某些保护或者使用32位地址寻址64位内存空间的能力。

GPU passthrough 最常见的应用是游戏,因为GPU passthrough是的虚拟机直接访问图形卡,因而游戏能够获得像直接运行在裸金属主机运行操作系统的性能。

BIOS和UEFI firmware

在设置GPU passthrough之前,首先需要确保处理器支持Intel VT-x 或AMD-V技术,这种技术是允许多个操作系统在处理器上同时执行的(虚拟机)技术。

  • 执行以下命令检查CPU的虚拟化硬件支持:

检查硬件虚拟化支持
grep --color -E "vmx|svm" /proc/cpuinfo
  • 在服务器主机的BIOS设置中启用 VT-dIOMMU 支持

IOMMU内核配置

内核激活IOMMU支持
Device Drivers --->
  [*] IOMMU Hardware Support --->
            Generic IOMMU Pagetable Support ----
      [ ]   AMD IOMMU support
      [*]   Support for Intel IOMMU using DMA Remapping Devices
      [*]     Support for Shared Virtual Memory with Intel IOMMU
      [*]     Enable Intel DMA Remapping Devices by default
      [*]     Enable Intel IOMMU scalable mode by default
      [*]     Intel IOMMU performance events
      < >   IOMMU Userspace API
      [*]   Support for Interrupt Remapping
      < >   Virtio IOMMU driver

备注

Deep dive into virtio-iommu 介绍了 virtio-iommu 技术,有待后续学习实践

virtio-iommu 代码解释

  • 编译内核并重启系统

编译内核
#编译内核映像和模块
make

#安装内核模块
make modules_install

# 复制编译好的内核
cp -iv arch/x86/boot/bzImage /boot/vmlinuz-6.10.5-lfs-12.2

# System.map 是内核符号文件,它将内核 API 的每个函数入口点和运行时数据结构映射到它们的地址。它被用于调查分析内核可能出现的问题。
cp -iv System.map /boot/System.map-6.10.5

# 内核配置文件 .config 由上述的 make menuconfig 步骤生成,包含编译好的内核的所有配置选项。最好能将它保留下来以供日后参考
cp -iv .config /boot/config-6.10.5

# 安装 Linux 内核文档
cp -r Documentation -T /usr/share/doc/linux-6.10.5
  • 重启系统以后检查dmesg信息(见 Intel VT-d快速起步 )可以看到内核支持IOMMU(但还没有激活,需要下一个内核参数传递来激活):

执行 dmesg 检查过滤IOMMU支持
dmesg | grep -e DMAR -e IOMMU

可以看到系统内核具备IOMMU支持(但还没有enable):

检查dmesg可以看到内核支持IOMMU功能
[    0.008773] ACPI: DMAR 0x000000007B7E7000 000294 (v01 HP     ProLiant 00000001 HP   00000001)
[    0.008805] ACPI: Reserving DMAR table memory at [mem 0x7b7e7000-0x7b7e7293]
[    2.560942] DMAR: Host address width 46
[    2.560945] DMAR: DRHD base: 0x000000fbffc000 flags: 0x0
[    2.560956] DMAR: dmar0: reg_base_addr fbffc000 ver 1:0 cap d2078c106f0466 ecap f020df
[    2.560961] DMAR: DRHD base: 0x000000c7ffc000 flags: 0x1
[    2.560966] DMAR: dmar1: reg_base_addr c7ffc000 ver 1:0 cap d2078c106f0466 ecap f020df
[    2.560971] DMAR: RMRR base: 0x00000079174000 end: 0x00000079176fff
[    2.560974] DMAR: RMRR base: 0x000000791f4000 end: 0x000000791f7fff
[    2.560978] DMAR: RMRR base: 0x000000791de000 end: 0x000000791f3fff
[    2.560980] DMAR: RMRR base: 0x000000791cb000 end: 0x000000791dbfff
[    2.560983] DMAR: RMRR base: 0x000000791dc000 end: 0x000000791ddfff
[    2.560987] DMAR-IR: IOAPIC id 10 under DRHD base  0xfbffc000 IOMMU 0
[    2.560991] DMAR-IR: IOAPIC id 8 under DRHD base  0xc7ffc000 IOMMU 1
[    2.560994] DMAR-IR: IOAPIC id 9 under DRHD base  0xc7ffc000 IOMMU 1
[    2.560996] DMAR-IR: HPET id 0 under DRHD base 0xc7ffc000
[    2.560999] DMAR-IR: Queued invalidation will be enabled to support x2apic and Intr-remapping.
[    2.561701] DMAR-IR: Enabled IRQ remapping in x2apic mode
[    2.734052] DMAR: No ATSR found
[    2.734056] DMAR: No SATC found
[    2.734060] DMAR: dmar0: Using Queued invalidation
[    2.734068] DMAR: dmar1: Using Queued invalidation
[    2.772752] DMAR: Intel(R) Virtualization Technology for Directed I/O
修订内核配置激活IOMMU
menuentry "GNU/Linux, Linux 6.10.5-lfs-12.2" {
  search --set=root --fs-uuid 81368941-bd96-4539-bf8e-7215d532a872
  linux   /boot/vmlinuz-6.10.5-lfs-12.2 root=PARTUUID=cf8d6f20-92bf-4f81-982e-50bea63e6ca3 ro iommu=pt intel_iommu=on pcie_acs_override=downstream,multifunction
  initrd  /boot/microcode.img
}

警告

上述内核参数 pcie_acs_override=downstream,multifunction 之前我的实践 IOMMU内核启动grub配置 没有使用,这个参数 不是必须的 ,但是是一个非常特殊的补丁:

再次重启后重新检查一次 dmesg 输出,就可以看到增加了一行 DMAR: IOMMU enabled (其他内容是一样的):

再次检查dmesg可以看到激活了IOMMU
[    0.008932] ACPI: DMAR 0x000000007B7E7000 000294 (v01 HP     ProLiant 00000001 HP   00000001)
[    0.008964] ACPI: Reserving DMAR table memory at [mem 0x7b7e7000-0x7b7e7293]
[    1.370946] DMAR: IOMMU enabled
[    2.564033] DMAR: Host address width 46
[    2.564036] DMAR: DRHD base: 0x000000fbffc000 flags: 0x0
[    2.564046] DMAR: dmar0: reg_base_addr fbffc000 ver 1:0 cap d2078c106f0466 ecap f020df
[    2.564052] DMAR: DRHD base: 0x000000c7ffc000 flags: 0x1
[    2.564058] DMAR: dmar1: reg_base_addr c7ffc000 ver 1:0 cap d2078c106f0466 ecap f020df
[    2.564062] DMAR: RMRR base: 0x00000079174000 end: 0x00000079176fff
[    2.564066] DMAR: RMRR base: 0x000000791f4000 end: 0x000000791f7fff
[    2.564069] DMAR: RMRR base: 0x000000791de000 end: 0x000000791f3fff
[    2.564072] DMAR: RMRR base: 0x000000791cb000 end: 0x000000791dbfff
[    2.564075] DMAR: RMRR base: 0x000000791dc000 end: 0x000000791ddfff
[    2.564079] DMAR-IR: IOAPIC id 10 under DRHD base  0xfbffc000 IOMMU 0
[    2.564083] DMAR-IR: IOAPIC id 8 under DRHD base  0xc7ffc000 IOMMU 1
[    2.564086] DMAR-IR: IOAPIC id 9 under DRHD base  0xc7ffc000 IOMMU 1
[    2.564089] DMAR-IR: HPET id 0 under DRHD base 0xc7ffc000
[    2.564092] DMAR-IR: Queued invalidation will be enabled to support x2apic and Intr-remapping.
[    2.564794] DMAR-IR: Enabled IRQ remapping in x2apic mode
[    2.739214] DMAR: No ATSR found
[    2.739217] DMAR: No SATC found
[    2.739220] DMAR: dmar0: Using Queued invalidation
[    2.739225] DMAR: dmar1: Using Queued invalidation
[    2.755479] DMAR: Intel(R) Virtualization Technology for Directed I/O

IOMMU groups

检查PCI设备映射到IOMMU组
#!/bin/bash
shopt -s nullglob
for g in `find /sys/kernel/iommu_groups/* -maxdepth 0 -type d | sort -V`; do
    echo "IOMMU Group ${g##*/}:"
    for d in $g/devices/*; do
        echo -e "\t$(lspci -nns ${d##*/})"
    done;
done;

IOMMU 组是可以传递给虚拟机的最小物理设备集

  • 从上述输出中可以过滤出 Tesla T10 对应信息:

获取到的 Tesla T10 设备 vfio-pci.ids
82:00.0 3D controller [0302]: NVIDIA Corporation TU102GL [Tesla T10 16GB / GRID RTX T10-2/T10-4/T10-8] [10de:1e37] (rev a1)

输出信息中 10de:1e37 就是需要隔离(passthrough)的 Tesla T10vfio-pci.ids

获取到的 Nvidia Tesla P10 GPU运算卡 设备 vfio-pci.ids
IOMMU Group 59:
    05:00.0 3D controller [0302]: NVIDIA Corporation GP102GL [Tesla P10] [10de:11b39] (rev a1)

对应 10de:11b39 就是隔离(passthrough) Nvidia Tesla P10 GPU运算卡vfio-pci.ids

VFIO

  • 内核配置支持 VFIO

配置内核支持VFIO
Device Drivers --->
  <M> VFIO Non-Privileged userpsace driver framework --->
    -*-   Support for the VFIO group /dev/vfio/$group_id
    -*-     Support for the VFIO container /dev/vfio/vfio
    [*]     VFIO No-IOMMU support
    [ ]   Export VFIO internals in DebugFS (NEW)
    VFIO support for PCI devices  ---> 
      <M>   Generic VFIO support for any PCI devices
      [*]     Generic VFIO PCI support for VGA devices
      [ ]   Generic VFIO PCI extensions for Intel graphics (GVT-d)
      < > VFIO support for VIRTIO NET PCI devices (NEW)
  • 编译内核

编译内核
#编译内核映像和模块
make

#安装内核模块
make modules_install

# 复制编译好的内核
cp -iv arch/x86/boot/bzImage /boot/vmlinuz-6.10.5-lfs-12.2

# System.map 是内核符号文件,它将内核 API 的每个函数入口点和运行时数据结构映射到它们的地址。它被用于调查分析内核可能出现的问题。
cp -iv System.map /boot/System.map-6.10.5

# 内核配置文件 .config 由上述的 make menuconfig 步骤生成,包含编译好的内核的所有配置选项。最好能将它保留下来以供日后参考
cp -iv .config /boot/config-6.10.5

# 安装 Linux 内核文档
cp -r Documentation -T /usr/share/doc/linux-6.10.5

备注

内核编译是将 VFIO 编译为模块,这样下面就可以配置模块加载时候绑定设备(根据 PCI IDs)

  • 再次获取 Tesla T10 PCI IDs : 使用 lspci -nn 命令可以扫描出PCI IDs(上文的脚本也行):

获取 Tesla T10 PCI IDs
lspci -nn | grep -i nvidia

输出信息:

获取 Tesla T10 PCI IDs ,可以看到 10de:1e37Tesla T10 的 PCI IDs
82:00.0 3D controller [0302]: NVIDIA Corporation TU102GL [Tesla T10 16GB / GRID RTX T10-2/T10-4/T10-8] [10de:1e37] (rev a1)
lspci -nn | grep -i nvidia
  • 将绑定到 VFIO 的设备ID添加到模块加载参数中,如果有多个设备id,则用 , 分隔,例如 ids=1002:687f,1002:aaf8 。以下是我的配置绑定到VFIO的 Tesla T10 :

将需要passthrough的 Tesla T10 设备IDs添加到VFIO的模块配置中
cat > /etc/modprobe.d/vfio.conf << "EOF"
options vfio-pci ids=10de:1e37
EOF
将需要passthrough的 Nvidia Tesla P10 GPU运算卡 设备IDs添加到VFIO的模块配置中
cat > /etc/modprobe.d/vfio.conf << "EOF"
options vfio-pci ids=10de:1b39
EOF

这里有一个 LFS(Linux from scratch) 自动加载模块的配置:

  • /etc/modules 中添加需要启动时自动加载的模块名(每行一个模块)

/etc/modules 设置自动加载的模块
tun
vfio-pci

配置参考了debian系统,但是目前还是没有解决启动后自动加载 待后续排查

  • 重启系统(目前我暂时采用 modprobe vfio-pci 手工加载),检查 dmesg | grep -i vfio 显示如下:

重启系统检查VFIO绑定信息
[  639.398688] VFIO - User Level meta-driver version: 0.3
[  639.404672] vfio_pci: add [10de:1e37[ffffffff:ffffffff]] class 0x000000/00000000
  • 检查确认模块 vfio-pci 是否正常连接了 Tesla T10 IDs 10de:1e37

通过 lspci 检查 Tesla T10 IDs 10de:1e37 是否正确使用了 vfio-pci 模块
lspci -nnk -d 10de:1e37

正确的输出应该如下:

通过 lspci 检查 Tesla T10 IDs 10de:1e37 使用了 vfio-pci 作为内核驱动
82:00.0 3D controller [0302]: NVIDIA Corporation TU102GL [Tesla T10 16GB / GRID RTX T10-2/T10-4/T10-8] [10de:1e37] (rev a1)
	Subsystem: NVIDIA Corporation Tesla T10 16GB [10de:1370]
	Kernel driver in use: vfio-pci
通过 lspci 检查 Nvidia Tesla P10 GPU运算卡 IDs 10de:1b39 是否正确使用了 vfio-pci 模块
lspci -nnk -d 10de:1b39

正确的输出应该如下:

通过 lspci 检查 Tesla T10 IDs 10de:1b39 使用了 vfio-pci 作为内核驱动
05:00.0 3D controller [0302]: NVIDIA Corporation GP102GL [Tesla P10] [10de:1b39] (rev a1)
        Subsystem: NVIDIA Corporation Device [10de:1217]
        Kernel driver in use: vfio-pci

一定要注意,这里GPU设备的内核驱动必须是 vfio-pci 才表明正确,这样才能passthrough给虚拟机使用

OVMF qemu

构建UEFI虚拟机
name=d2l

# 创建磁盘
qemu-img create -f qcow2 /sources/images/${name}.qcow 64G


qemu-system-x86_64 \
    -nodefaults \
    -enable-kvm \
    -cpu host,kvm=off \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -m 32G \
    -smp cores=4 \
    -boot order=c,once=d,menu=on \
    -drive file=/sources/images/${name}.qcow,if=virtio \
    -cdrom /sources/debian-cd_current_amd64_iso-dvd_debian-12.9.0-amd64-DVD-1.iso \
    -net nic,model=virtio,macaddr=52:54:00:00:00:02 -net bridge,br=br0 \
    -nographic \
    -vga none \
    -serial mon:stdio \
    -name "${name}"

这里尝试终端字符模式安装,但是会提示错误:

error: no suitable video mode found.
Booting in blind mode
  • 修订改进为VNC安装:

构建UEFI虚拟机(使用VNC)
name=d2l

# 创建磁盘
qemu-img create -f qcow2 /sources/images/${name}.qcow 64G


qemu-system-x86_64 \
    -nodefaults \
    -enable-kvm \
    -cpu host,kvm=off \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -m 32G \
    -smp cores=4 \
    -boot order=c,once=d,menu=on \
    -drive file=/sources/images/${name}.qcow,if=virtio \
    -cdrom /sources/debian-cd_current_amd64_iso-dvd_debian-12.9.0-amd64-DVD-1.iso \
    -net nic,model=virtio,macaddr=52:54:00:00:00:02 -net bridge,br=br0 \
    -vga std \
    -vnc :0 \
    -serial mon:stdio \
    -name "${name}"

# 默认终端提示
# VNC server running on 127.0.0.1:5900
# 如果需要VNC监听所有网络接口,则添加参数 -vnc :0 ,此时终端就看不到提示,但是使用VNC客户端可以连接
  • 完成安装以后采用如下方式运行(添加passthrough PGU)

运行UEFI虚拟机(使用VNC)
name=d2l

qemu-system-x86_64 \
    -nodefaults \
    -enable-kvm \
    -cpu host,kvm=off \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -m 32G \
    -smp cores=4 \
    -device vfio-pci,host=82:00.0 \
    -drive file=/sources/images/${name}.qcow,if=virtio \
    -net nic,model=virtio,macaddr=52:54:00:00:00:01 -net bridge,br=br0 \
    -vga std \
    -vnc :0 \
    -serial mon:stdio \
    -name "${name}"

# 默认终端提示
# VNC server running on 127.0.0.1:5900
# 如果需要VNC监听所有网络接口,则添加参数 -vnc :0 ,此时终端就看不到提示,但是使用VNC客户端可以连接

# lspci -nnk -d 10de:1e37
# 输出显示设备:
82:00.0 3D controller [0302]: NVIDIA Corporation TU102GL [Tesla T10 16GB / GRID RTX T10-2/T10-4/T10-8] [10de:1e37] (rev a1)
	Subsystem: NVIDIA Corporation Tesla T10 16GB [10de:1370]
	Kernel driver in use: vfio-pci
# 则在 qemu 运行参数中添加设备使用VFIO group id "82:00.0"
# 即 -device vfio-pci,host=82:00.0 \

然后登陆到虚拟机内部检查 lspci -nnv 可以看到GPU在虚拟机内部已经识别并被加载了驱动:

在虚拟机内部检查GPU
00:04.0 3D controller [0302]: NVIDIA Corporation TU102GL [Tesla T10 16GB / GRID RTX T10-2/T10-4/T10-8] [10de:1e37] (rev a1)
	Subsystem: NVIDIA Corporation Tesla T10 16GB [10de:1370]
	Physical Slot: 4
	Flags: fast devsel, IRQ 11
	Memory at c1000000 (32-bit, non-prefetchable) [size=16M]
	Memory at 1000000000 (64-bit, prefetchable) [size=16G]
	Memory at 1400000000 (64-bit, prefetchable) [size=32M]
	Capabilities: [60] Power Management version 3
	Capabilities: [78] Express Endpoint, MSI 00
	Capabilities: [c8] MSI-X: Enable- Count=6 Masked-
	Kernel modules: nouveau

问题排查

我在实践中,成功将上述 Tesla T10 ( -device vfio-pci,host=82:00.0 )PassThrough 给虚拟机,但是更换成 Nvidia Tesla P10 GPU运算卡 之后,传递 -device vfio-pci,host=05:00.0 却导致虚拟机无法启动(始终显示 Guest has not initialized the display (yet) )

检查系统日志显示, vfio-pci 添加 10de:1b39 是成功额,但是Guest始终无法初始化。

奇怪的是,一旦去掉传递 -device vfio-pci,host=05:00.0 虚拟机就能够正常启动

另外,如果我添加了 -device vfio-pci,host=05:00.0 参数,虽然虚拟机无法启动,但是物理主机中 dmesg 日志是显示:

[Sun Mar  2 23:42:42 2025] vfio-pci 0000:05:00.0: Enabling HDA controller

没有看到报错信息,看起来 vfio-pci 是正常隔离的

  • 实在没有办法,但是我也有点怀疑是我的 HPE ProLiant DL380 Gen9服务器 硬件问题,因为之前偶然会出现有关PCIe的硬件报错导致无法启动,所以我尝试将 Nvidia Tesla P10 GPU运算卡 更换一个PCIe插槽再次尝试

  • 更换PCIe插槽之后,执行 lspci -nn | grep -i nvidia 可以看到Tesla P10的 pcie.ids 不变,依然是 10de:1b39 :

更换PCIe插槽之后 Nvidia Tesla P10 GPU运算卡pcie.ids 不变,但是group更改为 0b:00.0
0b:00.0 3D controller [0302]: NVIDIA Corporation GP102GL [Tesla P10] [10de:1b39] (rev a1)
  • 修订启动 QEMU 命令:

更换PCIe插槽之后 qemu 使用不同的 vfio-pci 参数
name=d2l

qemu-system-x86_64 \
    -nodefaults \
    -enable-kvm \
    -cpu host,kvm=off \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -m 32G \
    -smp cores=4 \
    -device vfio-pci,host=0b:00.0 \
    -drive file=/sources/images/${name}.qcow,if=virtio \
    -net nic,model=virtio,macaddr=52:54:00:00:00:01 -net bridge,br=br0 \
    -vga std \
    -vnc :0 \
    -name "${name}"

还是没有解决启动问题,待查

下一步

现在一切就绪,在虚拟机内部就好像物理主机一样可以直接使用 Tesla T10 了,下一步:

参考