这里使用的 CPU 是 I9-9900K ,显卡是 2080Ti,宿主系统和虚拟机系统是 Debian GNU/Linux 12 。
首先要确认主板支持 VT-d 功能,并在 BIOS 中开启。
启动 IOMMU
IOMMU 是 Intel VT-d 和 AMD-Vi 的通用名称。
在宿主系统中启用 IOMMU 很简单,就是给 Linux 内核一个额外的启动参数。
可以直接修改 /etc/default/grub
中的 GRUB_CMDLINE_LINUX_DEFAULT
项。
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
只需要在后面加上 IOMMU 的参数即可。
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on"
然后更新 GRUB 使配置生效。
$ sudo update-grub
然后重启电脑,得到类似如下结果表示 IOMMU 启用成功。
$ sudo dmesg | grep -i iommu
...
[ 0.018093] DMAR: IOMMU enabled
...
[ 0.467648] pci 0000:00:00.0: Adding to iommu group 0
[ 0.467660] pci 0000:00:01.0: Adding to iommu group 1
[ 0.467674] pci 0000:00:14.0: Adding to iommu group 2
[ 0.467682] pci 0000:00:14.2: Adding to iommu group 2
[ 0.467690] pci 0000:00:14.3: Adding to iommu group 3
[ 0.467701] pci 0000:00:16.0: Adding to iommu group 4
[ 0.467716] pci 0000:00:1b.0: Adding to iommu group 5
[ 0.467726] pci 0000:00:1b.4: Adding to iommu group 6
...
查看 IOMMU Group
IOMMU Group 是硬件实现上的分组。
一个 IOMMU Group 是将物理设备直通给虚拟机的最小单位,如果不同的设备被分到一个 IOMMU Group 中,我们必须把他们都直通给虚拟机,否则会导致 IO 总线冲突,可能导致宿主机死机。
下面我们可以通过命令拿到 IOMMU Group 分组情况,这里只保留我们关心的设备。
$ sudo dmesg | grep -i iommu
...
[ 0.459200] pci 0000:00:01.0: Adding to iommu group 1
...
[ 0.459359] pci 0000:01:00.0: Adding to iommu group 1
[ 0.459363] pci 0000:01:00.1: Adding to iommu group 1
[ 0.459367] pci 0000:01:00.2: Adding to iommu group 1
[ 0.459371] pci 0000:01:00.3: Adding to iommu group 1
...
然后通过以下命令枚举 PCI 设备。
$ lspci
...
00:01.0 PCI bridge: Intel Corporation 6th-10th Gen Core Processor PCIe Controller (x16) (rev 0a)
...
01:00.0 VGA compatible controller: NVIDIA Corporation TU102 [GeForce RTX 2080 Ti] (rev a1)
01:00.1 Audio device: NVIDIA Corporation TU102 High Definition Audio Controller (rev a1)
01:00.2 USB controller: NVIDIA Corporation TU102 USB 3.1 Host Controller (rev a1)
01:00.3 Serial bus controller: NVIDIA Corporation TU102 USB Type-C UCSI Controller (rev a1)
...
其中 00:01.0
是 PCI 根设备,表示 CPU 提供的 PCI 控制器,而 01:00.0
~ 01:00.3
都是我们要直通的显卡。特别的, 2080Ti 带了一个 USB 控制器和声卡等设备,我们必须全部直通给虚拟机。
预留 PCI Device
这里我们使用的是 vfio-pci
方案。如果是老一点的系统,可能使用 pci-stub
更方便。
如果客户机所用设备插在 CPU 提供的 PCI-E 插槽中,如上面的 2080Ti ,其中 PCI 根设备 00:01.0 是 IOMMU Group 的一部分,这时不要将根设备绑定到
vfio-pci
。
Linux 现有的驱动机制是每个驱动在初始化时,自行去寻找没有初始化的,自己关注的硬件。
所以我们需要在其他驱动之前,使用 vfio-pci
接管设备。
第一步是使用命令查看当前设备的驱动占有情况以及设备的 ID 。
$ lspci -nnv
...
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation TU102 [GeForce RTX 2080 Ti] [10de:1e04] (rev a1) (prog-if 00 [VGA controller])
Subsystem: GALAX TU102 [GeForce RTX 2080 Ti] [1b4c:12ae]
Flags: bus master, fast devsel, latency 0, IRQ 187, IOMMU group 1
Memory at a4000000 (32-bit, non-prefetchable) [size=16M]
Memory at 90000000 (64-bit, prefetchable) [size=256M]
Memory at a2000000 (64-bit, prefetchable) [size=32M]
I/O ports at 4000 [size=128]
Expansion ROM at 000c0000 [disabled] [size=128K]
Capabilities: <access denied>
Kernel driver in use: nouveau
Kernel modules: nouveau
01:00.1 Audio device [0403]: NVIDIA Corporation TU102 High Definition Audio Controller [10de:10f7] (rev a1)
Subsystem: GALAX TU102 High Definition Audio Controller [1b4c:12ae]
Flags: bus master, fast devsel, latency 0, IRQ 17, IOMMU group 1
Memory at a5080000 (32-bit, non-prefetchable) [size=16K]
Capabilities: <access denied>
Kernel driver in use: snd_hda_intel
Kernel modules: snd_hda_intel
01:00.2 USB controller [0c03]: NVIDIA Corporation TU102 USB 3.1 Host Controller [10de:1ad6] (rev a1) (prog-if 30 [XHCI])
Subsystem: GALAX TU102 USB 3.1 Host Controller [1b4c:12ae]
Flags: fast devsel, IRQ 147, IOMMU group 1
Memory at a0000000 (64-bit, prefetchable) [size=256K]
Memory at a0040000 (64-bit, prefetchable) [size=64K]
Capabilities: <access denied>
Kernel driver in use: xhci_hcd
Kernel modules: xhci_pci
01:00.3 Serial bus controller [0c80]: NVIDIA Corporation TU102 USB Type-C UCSI Controller [10de:1ad7] (rev a1)
Subsystem: GALAX TU102 USB Type-C UCSI Controller [1b4c:12ae]
Flags: bus master, fast devsel, latency 0, IRQ 11, IOMMU group 1
Memory at a5084000 (32-bit, non-prefetchable) [size=4K]
Capabilities: <access denied>
...
每个设备名称后面用方括号括起来的 [10de:1e04]
等表示设备的 ID 。
每个设备下面的 Kernel driver in use:
表示占有设备的驱动。
那么显然,我们的显卡目前被驱动 nouveau
、snd_hda_intel
和 xhci_hcd
占用。
那么为了让 vfio-pci
接管设备,我们首先要启动相关模块。
直接在 /etc/initramfs-tools/modules
中添加相应的模块名即可。
# List of modules that you want to include in your initramfs.
# They will be loaded at boot time in the order below.
...
vfio_pci
vfio
vfio_iommu_type1
然后在 /etc/modprobe.d
中添加 vfio-pci
的配置文件 vfio.conf
。
# Bind GPU device to vfio-pci for virtual machine.
options vfio-pci ids=10de:1e04,10de:10f7,10de:1ad6,10de:1ad7 disable_vga=1
# Ensure that vfio-pci is initialized before GPU driver.
softdep nouveau pre: vfio-pci
softdep snd_hda_intel pre: vfio-pci
softdep xhci_pci pre: vfio-pci
其中 ids
选项表示我们想要占有的设备 ID ,可以使用上面提到的命获得。
disable_vga
表示禁止使用此显卡作为宿主机系统 VGA 设备,可以使用以下命令查看某个显卡是否被当做宿主机系统 VGA 设备,其中得到 0
表示否,得到 1
表示是。
$ cat /sys/bus/pci/devices/0000\:00\:02.0/boot_vga
1
$ cat /sys/bus/pci/devices/0000\:01\:00.0/boot_vga
0
其中前者是核显,后者是需要直通的显卡。
部分 BIOS 默认设置会在存在独显时关闭核显,或者优先使用高性能显卡作为宿主机系统 VGA 设备。
最下面的三行 softdep
表示使 vfio-pci
模块在可能占有设备的其他模块之前加载,确保可以更早的占有设备,防止占有失败。这里的三个模块分别对应之前查询到的三个驱动。
最后使用下面的命令使以上两个配置文件生效即可。
$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.1.0-10-amd64
可以再次使用命令检查所有的设备是否被 vfio-pci
正确占有。
$ lspci -nnv
...
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation TU102 [GeForce RTX 2080 Ti] [10de:1e04] (rev a1) (prog-if 00 [VGA controller])
Subsystem: GALAX TU102 [GeForce RTX 2080 Ti] [1b4c:12ae]
Flags: fast devsel, IRQ 255, IOMMU group 2
Memory at a4000000 (32-bit, non-prefetchable) [disabled] [size=16M]
Memory at 90000000 (64-bit, prefetchable) [disabled] [size=256M]
Memory at a0000000 (64-bit, prefetchable) [disabled] [size=32M]
I/O ports at 4000 [disabled] [size=128]
Expansion ROM at a5000000 [disabled] [size=512K]
Capabilities: <access denied>
Kernel driver in use: vfio-pci
Kernel modules: nouveau
01:00.1 Audio device [0403]: NVIDIA Corporation TU102 High Definition Audio Controller [10de:10f7] (rev a1)
Subsystem: GALAX TU102 High Definition Audio Controller [1b4c:12ae]
Flags: fast devsel, IRQ 255, IOMMU group 2
Memory at a5080000 (32-bit, non-prefetchable) [disabled] [size=16K]
Capabilities: <access denied>
Kernel driver in use: vfio-pci
Kernel modules: snd_hda_intel
01:00.2 USB controller [0c03]: NVIDIA Corporation TU102 USB 3.1 Host Controller [10de:1ad6] (rev a1) (prog-if 30 [XHCI])
Subsystem: GALAX TU102 USB 3.1 Host Controller [1b4c:12ae]
Flags: bus master, fast devsel, latency 0, IRQ 18, IOMMU group 2
Memory at a2000000 (64-bit, prefetchable) [size=256K]
Memory at a2040000 (64-bit, prefetchable) [size=64K]
Capabilities: <access denied>
Kernel driver in use: vfio-pci
Kernel modules: xhci_pci
01:00.3 Serial bus controller [0c80]: NVIDIA Corporation TU102 USB Type-C UCSI Controller [10de:1ad7] (rev a1)
Subsystem: GALAX TU102 USB Type-C UCSI Controller [1b4c:12ae]
Flags: fast devsel, IRQ 255, IOMMU group 2
Memory at a5084000 (32-bit, non-prefetchable) [disabled] [size=4K]
Capabilities: <access denied>
Kernel driver in use: vfio-pci
...
确保所有需要直通的设备 Kernel driver in use:
为 vfio-pci
即可。
如果与预期不同,重试上面的操作,不要直接进行下一步,否则可能损伤硬件。
配置 KVM
这里直接使用 virt-install
创建虚拟机。
$ sudo virt-install \
--name=linux-vm \
--os-variant=debian11 \
--vcpu=4 \
--ram=16384 \
--disk path=/vol/kvm/linux-vm.img,size=128 \
--graphics vnc,listen=0.0.0.0 \
--cdrom /vol/kvm/debian-12.0.0-amd64-DVD-1.iso \
--features kvm_hidden=on \
--network bridge:virbr0 \
--boot uefi
Starting install...
Allocating 'linux-vm.img' | 18 MB 00:00:05
Creating domain... | 0 B 00:00:00
Running graphical console command: virt-viewer --connect qemu:///system --wait linux-vm
Domain is still running. Installation may be in progress.
You can reconnect to the console to complete the installation process.
注意使用 kvm_hidden=on
防止驱动发现虚拟机报错,以及使用 UEFI 正确识别设备。
先安装系统,然后关闭虚拟机并使用命令编辑虚拟机 XML 配置文件。
$ sudo EDITOR=vim virsh edit linux-vm
添加以下配置,将直通的设备映射给虚拟机。
其中 source
的 address
表示宿主机侧的物理位置参数,也就是上面设备名前的 01:00.0
,这里用 bus='0x01' slot='0x00' function='0x0'
表示。而外面的 address
是虚拟机侧的物理位置参数,注意 bus
不要与其他设备冲突。因为显卡由不同的设备组成,所以需要使用 multifunction='on'
。
<domain type='kvm'>
...
<devices>
...
<hostdev mode='subsystem' type='pci' managed='no'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</source>
<address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0' multifunction='on'/>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='no'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x1'/>
</source>
<address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x1'/>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='no'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x2'/>
</source>
<address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x2'/>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='no'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x3'/>
</source>
<address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x3'/>
</hostdev>
</devices>
</domain>
然后启动虚拟机安装设备驱动即可,注意在安装 NVIDIA 官方驱动时,可能会提示需要安装部分软件包。
$ sudo apt-get install gcc
$ sudo apt-get install make
$ sudo apt-get install linux-headers-[uname -r] build-essential
如果在安装过程中提示因为 Secure Boot 导致内核加载失败。
ERROR: The kernel module failed to load. Secure boot is enabled on this system, so this is likely because it was not signed by a key that is trusted by the kernel. Please try installing the driver again, and sign the kernel module when prompted to do so.
或者在安装结束后发现 nvidia-smi
找不到驱动。
$ nvidia-smi
NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running.
可以通过编辑虚拟机 XML 配置文件,关闭 Secure Boot 解决。即添加以下项。
<domain type='kvm'>
...
<os firmware='efi'>
...
<firmware>
<feature enabled='no' name='secure-boot'/>
</firmware>
</os>
</domain>
注意,在修改 BIOS 相关配置后,需要更新 NVRAM ,即第一次启动时添加以下参数。
$ sudo virsh start linux-vm --reset-nvram
Domain 'linux-vm' started
如果在安装系统之后更新 NVRAM ,可能会导致 BIOS 引导丢失,需要进入 BIOS 手动从文件引导(Boot From File)。
最后,使用 nvidia-smi
找到设备,大功告成。
$ nvidia-smi
Wed Aug 16 15:18:04 2023
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.98 Driver Version: 535.98 CUDA Version: 12.2 |
|-----------------------------------------+----------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+======================+======================|
| 0 NVIDIA GeForce RTX 2080 Ti Off | 00000000:05:00.0 Off | N/A |
| 33% 57C P0 65W / 250W | 0MiB / 11264MiB | 0% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+
+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| No running processes found |
+---------------------------------------------------------------------------------------+