Linux – KVM 显卡直通或其他设备直通

这里使用的 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: 表示占有设备的驱动。

那么显然,我们的显卡目前被驱动 nouveausnd_hda_intelxhci_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

添加以下配置,将直通的设备映射给虚拟机。

其中 sourceaddress 表示宿主机侧的物理位置参数,也就是上面设备名前的 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                                                           |
+---------------------------------------------------------------------------------------+

参考

发表回复