在QEMU中运行GPU passthrough的Debian
备注
本文实践是在 BLFS QEMU 基础上完成:
由于是完全手搓操作系统,所以部分配置会采用比较"原始"的命令行完成,例如内核参数采用直接修订
/boot/grub/grub.cfg
而不是类似 Ubuntu Linux 修订/etc/default/grub
已经完成 在QEMU中运行debian 实践,本文在此基础上激活 IOMMU 通过 Open Virtual Machine Firmware(OMVF) 来实现GPU passthrough,部分实践参考之前经验 采用OVMF实现passthrough GPU和NVMe存储 。
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-d
和IOMMU
支持
IOMMU内核配置
IOMMU架构 <= 原理
IOMMU内核配置采用 Gentoo 虚拟化 建议的内核配置并做了一点调整:
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
编译内核并重启系统
#编译内核映像和模块
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 | grep -e DMAR -e IOMMU
可以看到系统内核具备IOMMU支持(但还没有enable):
[ 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
配置内核参数: 对于 LFS(Linux from scratch) 直接修订
/boot/grub/grub.cfg
配置:
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配置 没有使用,这个参数 不是必须的 ,但是是一个非常特殊的补丁:
如果物理主机上有 相同厂商的相同型号多个PCIe设备 ,那么这些相同设备
GPU
/NVMe
会分配到相同的iommu group和相同id这就带来一个问题: 由于PCIe passthrough是以IOMMU group的
vfio-pci.ids
来对物理Host主机屏蔽PCIe设备的,这就导致了会将所有同厂商同型号设备全部屏蔽(见 Open Virtual Machine Firmware(OMVF) 实践中主机设备中3块vfio-pci.ids=144d:a80a
同时被屏蔽的案例)pcie_acs_override=downstream,multifunction
可以将相同vfio-pci.ids
设备尽可能分配到不同组的不同ids,以便能够控制哪些设备保留在Host,哪些设备passthrough给虚拟机使用pcie_acs_override=downstream,multifunction
餐宿讨论见 Why is pcie_acs_override=downstream,multifunction discouraged?archlinux wiki: PCI passthrough via OVMF: Ensuring that the groups are valid 做了详细说明,并引用了 IOMMU Groups, inside and out
再次重启后重新检查一次 dmesg
输出,就可以看到增加了一行 DMAR: IOMMU enabled
(其他内容是一样的):
[ 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
执行以下命令确认IOMMU组( 采用OVMF实现passthrough GPU和NVMe存储 ):
#!/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 对应信息:
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 T10 的 vfio-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
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(上文的脚本也行):
输出信息:
82:00.0 3D controller [0302]: NVIDIA Corporation TU102GL [Tesla T10 16GB / GRID RTX T10-2/T10-4/T10-8] [10de:1e37] (rev a1)
类里,获取 Nvidia Tesla P10 GPU运算卡 PCI IDs:
lspci -nn | grep -i nvidia
将绑定到 VFIO 的设备ID添加到模块加载参数中,如果有多个设备id,则用
,
分隔,例如ids=1002:687f,1002:aaf8
。以下是我的配置绑定到VFIO的 Tesla T10 :
cat > /etc/modprobe.d/vfio.conf << "EOF"
options vfio-pci ids=10de:1e37
EOF
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
显示如下:
[ 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 IDs10de: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
检查确认模块
vfio-pci
是否正常连接了 Nvidia Tesla P10 GPU运算卡 IDs10de:1b39
lspci
检查 Nvidia Tesla P10 GPU运算卡 IDs 10de:1b39
是否正确使用了 vfio-pci
模块lspci -nnk -d 10de:1b39
正确的输出应该如下:
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
首先完成 BLFS OVMF (目前我采用从 Ubuntu Linux 系统复制的
OVMF_CODE.fd
)修改 在QEMU中运行debian 运行参数,添加
-bios /usr/share/OVMF/OVMF_CODE.fd
参数:
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安装:
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)
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在虚拟机内部已经识别并被加载了驱动:
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.ids
不变,但是group更改为 0b:00.0
0b:00.0 3D controller [0302]: NVIDIA Corporation GP102GL [Tesla P10] [10de:1b39] (rev a1)
修订启动 QEMU 命令:
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 了,下一步: