GPU穿透

通过IOMMU(IOMMU 是 Intel VT-d 和 AMD-Vi 的通用名称),并给主机添加VFIO模块。 使得GPU在主机上以vfio驱动,并将PCI穿透到虚拟机中使用。

开启IOMMU

在 BIOS 设置中启用 Intel VT-d 或 AMD-Vi,对于Intel的CPU,设置内核参数 intel_iommu=on iommu=pt

因为穿透以IOMMU分组作为最小单位。首先查看IOMMU分组和设备信息,通过以下脚本,确认要穿透的设备与主机要使用的设备不在同一个分组中。

iommu.sh

#!/bin/bash
for d in /sys/kernel/iommu_groups/*/devices/*; do
  n=${d#*/iommu_groups/*}; n=${n%%/*}
  printf 'IOMMU Group %s ' "$n"
  lspci -nns "${d##*/}"
done

静态屏蔽

如果主机有2个或以上显卡,主机不使用该GPU,专给虚拟机使用,比较简单的方式是直接从主机静态绑定设备到vfio-pci。

具体执行有2种方式

优先绑定vfio-pci

/etc/modprobe.d/vfio.conf 中写入以下内容

# Bind GPU device to vfio-pci for virtual machine.
options vfio-pci ids=10de:2882,10de:22be 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

第一行,将GPU设备绑定到vfio-pci,ids=10de:2882,10de:22be是显卡的PCI ID,从iommu.sh执行结果可以看到,disable_vga=1表示禁用VGA功能。 后面三行,确保vfio-pci在GPU驱动程序之前初始化。

屏蔽显卡驱动

还有一种做法是/etc/modprobe.d/blacklist.conf

blacklist nouveau

把显卡驱动加入黑名单。lspci -k | grep -A 3 "VGA"可以查看显卡驱动。但是这种做法对于相同驱动的显卡则不好分辨。

动态分离

主机使用GPU

如果主机也要使用该GPU,可以使用动态分离设备的方式。

为了方便分离,显卡需要不被Xorg使用,否则切换穿透时会提示被占用,就无法分离给虚拟机使用。还要先停止Xorg,就不方便。

为此,NVIDIA显卡驱动安装的时候选择不要生成Xorg配置。如果已经安装了Xorg配置,可以删除/etc/X11/xorg.conf/usr/share/X11/xorg.conf.d/nvidia-drm-outputclass.conf。 如果还是不行,可以使用fuser命令,或者lsof命令,确定哪个程序占用了GPU

sudo fuser -v /dev/nvidia*
sudo lsof /dev/nvidia*

分离

#!/bin/bash

gpu="0000:01:00.0"
aud="0000:01:00.1"
gpu_vd="$(cat /sys/bus/pci/devices/$gpu/vendor) $(cat /sys/bus/pci/devices/$gpu/device)"
aud_vd="$(cat /sys/bus/pci/devices/$aud/vendor) $(cat /sys/bus/pci/devices/$aud/device)"

function bind_vfio {
  echo "$gpu" > "/sys/bus/pci/devices/$gpu/driver/unbind"
  echo "$aud" > "/sys/bus/pci/devices/$aud/driver/unbind"
  echo "$gpu_vd" > /sys/bus/pci/drivers/vfio-pci/new_id
  echo "$aud_vd" > /sys/bus/pci/drivers/vfio-pci/new_id
}
 
function unbind_vfio {
  echo "$gpu_vd" > "/sys/bus/pci/drivers/vfio-pci/remove_id"
  echo "$aud_vd" > "/sys/bus/pci/drivers/vfio-pci/remove_id"
  echo 1 > "/sys/bus/pci/devices/$gpu/remove"
  echo 1 > "/sys/bus/pci/devices/$aud/remove"
  echo 1 > "/sys/bus/pci/rescan"
  # 或者也可以像上面vfio-pci那样直接绑定,驱动可能是nvidia,声卡驱动可能是snd_hda_intel
}

还有一个virsh命令,已经把上面的步骤封装了,更直接

virsh nodedev-detach pci_0000_01_00_0
virsh nodedev-detach pci_0000_01_00_1

反过来

virsh nodedev-reattach pci_0000_01_00_0
virsh nodedev-reattach pci_0000_01_00_1

hooks

可以使用hooks在虚拟机启动和关闭时自动分离和绑定设备。

sudo wget 'https://raw.githubusercontent.com/PassthroughPOST/VFIO-Tools/master/libvirt_hooks/qemu' \
     -O /etc/libvirt/hooks/qemu
sudo chmod +x /etc/libvirt/hooks/qemu

创建 /etc/libvirt/hooks/kvm.conf

## Virsh devices
VIRSH_GPU_VIDEO=pci_0000_01_00_0
VIRSH_GPU_AUDIO=pci_0000_01_00_1

假设虚拟机名为win10。 创建 /etc/libvirt/hooks/qemu.d/win10/prepare/begin/bind_vfio.sh

#!/bin/bash

## Load the config file
source "/etc/libvirt/hooks/kvm.conf"

## Load vfio
modprobe vfio
modprobe vfio_iommu_type1
modprobe vfio_pci

## Unbind gpu from nvidia and bind to vfio
virsh nodedev-detach $VIRSH_GPU_VIDEO
virsh nodedev-detach $VIRSH_GPU_AUDIO

创建 /etc/libvirt/hooks/qemu.d/win10/prepare/end/unbind_vfio.sh`

#!/bin/bash

## Load the config file
source "/etc/libvirt/hooks/kvm.conf"

## Unbind gpu from vfio and bind to nvidia
virsh nodedev-reattach $VIRSH_GPU_VIDEO
virsh nodedev-reattach $VIRSH_GPU_AUDIO

## Unload vfio
modprobe -r vfio_pci
modprobe -r vfio_iommu_type1
modprobe -r vfio

虚拟机添加PCI

通过virt-manager和virsh edit win10给虚拟机win10添加PCI设备,参考xml

  <devices>
  ...
    <hostdev mode="subsystem" type="pci" managed="yes">
      <source>
        <address domain="0x0000" bus="0x01" slot="0x00" function="0x0"/>
      </source>
      <address type="pci" domain="0x0000" bus="0x05" slot="0x00" function="0x0"/>
    </hostdev>
    <hostdev mode="subsystem" type="pci" managed="yes">
      <source>
        <address domain="0x0000" bus="0x01" slot="0x00" function="0x1"/>
      </source>
      <address type="pci" domain="0x0000" bus="0x06" slot="0x00" function="0x0"/>
    </hostdev>
  ...
  </devices>