虚拟机显卡直通 (PCI passthrough via OVMF)

虚拟机对显卡的支持一向并不是非常完美,比如 VirtualBox 虚拟机设置,显存部分一般最多拉到 128MB,是虚拟的,无法用物理机的显卡,哪怕这张显卡是闲置的

VMware 很久没用了,据我所知,也是不支持物理显卡(这里不考虑企业级软件)

而 KVM + QEMU 却可以通过 PCI passthrough 的方式将物理机的显卡直通给虚拟机用,非常厉害

前提准备

下面所有操作都是基于 Arch Linux,其他系统未测试,未来也不会测试

因为本质上还是虚拟机,所以要确保 BIOS 设置好了开启虚拟化

接着就是要开启 IOMMU,这个也要硬件的支持

  • 如果是 Intel 的 CPU,修改内核参数 intel_iommu=on iommu=pt
  • 如果是 AMD 的 CPU,默认就有一些设置,因此只需加 iommu=pt(Yes?)

修改完内核参数要重新跑 sudo grub-mkconfig -o /boot/grub/grub.cfg,重启生效

然后就是要拿到硬件的一些 id,这些 id 将会作为硬件的唯一标识在直通的过程中被使用到(这个脚本可以在 Arch Wiki 找到)

1
2
3
4
5
6
7
8
#!/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;

PCI passthrough 的处理是通过将显卡从你的宿主机隔离出来,其中又有多种处理方式

多显卡

如果你的电脑是多显卡,并且 Linux 系统没有独显需求,就可以使用这种方法

这种情况多数出现在笔记本,当然台式机也有,只是很多 CPU 型号都把核显砍了

如果有独显,CPU 里又已经有核显了,厂商会进行一些优化,平时用核显,但是当有专业需求的时候,走的就是独显;自己也可以通过 Optimus 或者 Bumblebee 来进行控制

我的笔记本之前只拿来写写 Java,就是用 nvidia-xrun 把整个独显都禁用掉,neofetch 也查不到,相当于不通电,这样来稍微省点电池消耗

如果宿主机跑的是 Linux,虚拟机跑的是 Windows,那就可以通过这种方式来进行设置

这种方法可以实现物理机和虚拟机同时存在,物理机显示用核显,因为都不用独显了,所以可以在物理机系统启动时就把显卡隔离出来,这个就需要修改 Linux 内核参数

将上面准备阶段拿到的 id,写进内核参数 vfio-pci.ids=xxx,也要重新生成 grub 配置,也是重启生效

接着修改 /etc/mkinitcpio.conf

1
2
3
MODULES=(... vfio_pci vfio vfio_iommu_type1 vfio_virqfd ...)
...
HOOKS=(... modconf ...)

修改完这个后也要重新生成 sudo mkinitcpio -p linux,同样是重启生效

然后多显卡的前提配置就完成了

单显卡

单显卡有点特殊,物理机输出到显示器也要显卡,所以不能提前将其隔离

QEMU 提供了一个 hook 功能,当虚拟机执行到某个阶段时,可以执行一些特定的脚本

因此思路就有了

  • 系统开机,正常使用显卡显示
  • 开启虚拟机时,执行 hook,释放掉占用的资源,将显卡在物理机上隔离出来
  • 关闭虚拟机时,执行 hook,重新把显卡模块启用,开启 DM

首先 id 要写在一个单独配置文件 /etc/modprobe.d/vfio.conf 里,这个起到的作用就是可以动态进行模块的加载

1
options vfio-pci ids=xxx

接着要准备好 hook 脚本,路径是 /etc/libvirt/hooks/qemu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash

OBJECT="$1"
OPERATION="$2"

if [[ $OBJECT == "win10" ]]; then
case "$OPERATION" in
"prepare")
prepare.sh 2>&1 | tee -a /var/log/libvirt/hooks.log
;;

"release")
release.sh 2>&1 | tee -a /var/log/libvirt/hooks.log
;;
esac
fi

这里的 win10 是虚拟机的名称

开启虚拟机的监听脚本 prepare.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
set -x

systemctl stop lightdm

echo efi-framebuffer.0 > /sys/bus/platform/drivers/efi-framebuffer/unbind

modprobe -r nvidia_drm
modprobe -r nvidia_modeset
modprobe -r nvidia_uvm
modprobe -r nvidia

virsh nodedev-detach pci_0000_xxx
virsh nodedev-detach pci_0000_xxx

modprobe vfio_pci
modprobe vfio
modprobe vfio_iommu_type1
modprobe vfio_virqfd

其中的 pci_0000_xxx 是在查 id 的阶段拿到的,比如输出

1
06:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA104 [GeForce RTX 3060 Ti] [10de:2486] (rev a1)

options vfio-pci ids=10de:2486virsh nodedev-detach pci_0000_06_00_0

关闭虚拟机的监听脚本 release.sh,关闭其实就是一个反向的回滚操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
set -x

modprobe -r vfio_pci
modprobe -r vfio
modprobe -r vfio_iommu_type1
modprobe -r vfio_virqfd

virsh nodedev-reattach pci_0000_xxx

echo efi-framebuffer.0 > /sys/bus/platform/drivers/efi-framebuffer/bind

modprobe nvidia_drm
modprobe nvidia_modeset
modprobe nvidia_uvm
modprobe nvidia

systemctl start lightdm

这些脚本需要在安装完系统后再设置,或者先换别的名字,不然安装的时候就会被执行,然后黑屏

这个就是单显卡的前提配置

系统安装及测试

多显卡进行系统安装时可以直接把显卡设置进去

单显卡则不可以,需要先安装系统,如果显卡设置进去,那物理机也要用,虚拟机也要用,就会卡住

平时 Windows 在系统安装完成后会自动在系统更新那里帮用户安装好显卡驱动,但是显卡直通这种方式最好是手动进行安装,Windows 不一定识别得出来,安装完驱动后,一切都和正常使用就没什么区别了