参考文献:
- Ubuntu配置GPU直传kvm虚拟机 – CodeAntenna
- KVM虚拟机GPU直通,step by step – 机械意志 (mechanical-consciousness.com)
- lspci的输出简单分析 – 成蹊0xc000 – 博客园 (cnblogs.com)
- PCI passthrough via OVMF – Arch Linux 中文维基 (archlinuxcn.org)
- Win10/11 如何开启 第二屏幕/副屏/虚拟显示器,让平板成为副屏 – 知乎 (zhihu.com)
首先声明,本文的背景是在两张同型号的3090ti中选择一张进行直通,在整个直通过程中,上面的参考文献给与了我很大的帮助,本篇内容是我基于他们描述完整操作后的记录,其中不乏对原文的直接摘抄,但也结合了我个人的机器情况进行了重新梳理,并进行了多次可行性验证,因为每个人的机器情况可能都大不相同,希望我的记录能给和我相同配置的同学一些帮助。如果有不懂的命令,建议大家查一下GPT,可能理解起来会快很多。
直通步骤:
1. 确定是否支持虚拟化:
确定本机是否支持VT-P虚拟化,一般需要主板BIOS开启VT-P。如果不能进BIOS确认,也可以在终端里确认。
检查CPU是否支持虚拟化,运行如下命令:
$ egrep -c '(svm|vm)' /proc/cpuinfo
如果显示为0,则不支持虚拟化。
系统配置:Ubuntu22.04.1 LTS,GPU 3090 Ti*2,CPU i7 13700K,主板 华硕Z790-P
2. 在宿主机系统中启用iommu组:
打开/etc/default/grub,找到GRUB_CMDLINE_LINUX_DE FAULT,修改为下面内容:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_iommu=on"
接着我们需要更新grub:
$ sudo update-grub -u
然后重启电脑,重启完后我们需要检查iommu有没有被正确启用:
$ dmesg | grep -i iommu
如果你在输出中看到很多pci xxxx:xx:xx.x: Adding to iommu group xx
,那么恭喜你,第一步完成了。
系统中的所有设备只能按照iommu group为单位分配给宿主机或者虚拟机。因此如果你看到两个不相关的设备在一个group里,那也没招,你只能给这俩设备一起丢虚拟机里去。
很可惜的是,Linux系统并不支持预留iommu group。
iommu group是硬件实现上的分组。在Linux的实现中,只认得各个在总线上的硬件,并挨个挨个的启动起来。
我们要做的就是阻止Linux内核在启动时初始化某些组里的硬件。
3. 寻找我们需要预留的iommu组:
首先确认我们显卡所在的PCIE总线和产品型号:
$ lspci -nnv | grep -i nvidia
我的电脑上输出如下:
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2203] (rev a1) (prog-if 00 [VGA controller])Kernel driver in use: nvidiaKernel modules: nvidiafb, nouveau, nvidia_drm, nvidia01:00.1 Audio device [0403]: NVIDIA Corporation GA102 High Definition Audio Controller [10de:1aef] (rev a1)05:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2203] (rev a1) (prog-if 00 [VGA controller])Kernel modules: nvidiafb, nouveau, nvidia_drm, nvidia05:00.1 Audio device [0403]: NVIDIA Corporation GA102 High Definition Audio Controller [10de:1aef] (rev a1)
可以看到输出分成两部分,编号加设备描述。01:00.0
和01:00.1
是编号通过冒号 “:” 又分成了三部分,第一个部分是PCIe的 domain ID,第二个部分是 bus ID,第三个部分是 device id.function id。PCI设备的组织形式是一个树形,这表示一个domain可以包含多个bus,一个bus又包含了多个device,一个device又包含多个function。
0300
是一个用来区分不同设备的编号,网络设备、存储设备、多媒体设备、显卡都有自己的编号。
10de:2203
这是一个厂商特有的编号,10de是 Nvidia的编号(Vendor ID),Nvidia所有的设备都是使用这一编号,2203 表示这是一个3090Ti显卡。
以上参考:lspci的输出简单分析 – 成蹊0xc000 – 博客园 (cnblogs.com),可以在这个博客里继续查找自己的显卡信息。
接下来我们需要找出我们的设备所在的iommu group:
$ sudo dmesg | grep iommu | grep 01:00.0
输出如下:
[0.536462] pci 0000:01:00.0: Adding to iommu group 17$ sudo dmesg | grep iommu | grep 05:00.0[0.536491] pci 0000:05:00.0: Adding to iommu group 20
确认iommu group中的所有设备:
$ sudo dmesg | grep "iommu group 17"
输出如下:
[0.536462] pci 0000:01:00.0: Adding to iommu group 17[0.536467] pci 0000:01:00.1: Adding to iommu group 17
4. 屏蔽显卡
4.1 型号屏蔽法:
如果只有一张显卡,或者两张显卡的ID不同,可以直接用pci-stub或者vfio-pci的方法来屏蔽即可,修改/etc/default/grub
:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_iommu=on pci-stub.ids=10de:2203,10de:1aef"或者GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_iommu=on vfio-pci.ids=10de:2203,10de:1aef"
更新内核,并重启:
$ sudo update-grub -u
如果屏蔽成功,内核驱动就会显示如下:
$ lspci -nnv | grep -E "(^\S|Kernel driver in use)" | grep 01:00 -A 1
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2203] (rev a1) (prog-if 00 [VGA controller])Kernel driver in use: vfio-pci01:00.1 Audio device [0403]: NVIDIA Corporation GA102 High Definition Audio Controller [10de:1aef] (rev a1)Kernel driver in use: vfio-pci
否则下面还是驱动被nvidia接管的状态(其实nvidia-smi就能看了):
$ lspci -nnv | grep -E "(^\S|Kernel driver in use)" | grep 01:00 -A 1
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2203] (rev a1) (prog-if 00 [VGA controller])Kernel driver in use: nvidia01:00.1 Audio device [0403]: NVIDIA Corporation GA102 High Definition Audio Controller [10de:1aef] (rev a1)Kernel driver in use: snd_hda_intel
4.2 脚本屏蔽法
当然如果两张显卡都是同一个型号,是不能用上面的方法屏蔽的。
这个时候我们要为显卡指定驱动:
$ sudo vim /etc/initramfs-tools/scripts/init-top/vfio.sh
脚本内容为:
#!/bin/shecho "vfio-pci" > /sys/bus/pci/devices/0000:01:00.0/driver_overrideecho "vfio-pci" > /sys/bus/pci/devices/0000:01:00.1/driver_override#echo "0000:01:00.0" > /sys/bus/pci/drivers/vfio-pci/bind # 这里我没有加这个绑定,有人加了这两句,但我注释了,因为本地找不到这个文件夹#echo "0000:01:00.1" > /sys/bus/pci/drivers/vfio-pci/bind # 需要modprobe -i vfio-pci之后才有这个文件夹,但不加仍然可用exit 0
给脚本增加权限,否则无法执行:
$ sudo chmod 744 /etc/initramfs-tools/scripts/init-top/vfio.sh
完成后刷新initramfs,这个命令会执行上面的脚本:
$ sudo update-initramfs -u -k all
输出下面的结果,重启就可以正常屏蔽了。
update-initramfs: Generating /boot/initrd.img-6.2.0-31-genericupdate-initramfs: Generating /boot/initrd.img-6.2.0-26-generic
重启配置成功后按照上一节的命令就能看到显卡的驱动被vfio-pci接管了,nvidia-smi也能发现显卡已经无法识别了。
如果想恢复显卡的挂载,就把脚本中的两行echo...
注释掉,并刷新initramfs
,重启机器即可。
4.3 开机脚本屏蔽法
有些地方用下面的方法成功了,但我试了没有效果,我把方法留在这里供大家尝试:
$ sudo vim /usr/bin/vfio-pci-override.sh
脚本的内容:
#!/bin/shif [ ! -z "$(ls -A /sys/class/iommu)" ]; then echo "vfio-pci" > /sys/bus/pci/devices/0000:01:00.0/driver_override echo "vfio-pci" > /sys/bus/pci/devices/0000:01:00.1/driver_overridefimodprobe -i vfio-pci
再给脚本增加运行权限,否则启动时是不能运行的:
$ sudo chmod 744 /usr/bin/vfio-pci-override.sh
最后在/etc/modprobe.d/vfio.conf
内指定我们的脚本路径。
$ sudo vim /etc/modprobe.d/vfio.conf
install vfio-pci /usr/bin/vfio-pci-override.sh
Arch Linux的官方中文文档也有详细的解释,但实际操作和Ubuntu有差异,放在这里作为一个参考资料。PCI passthrough via OVMF – Arch Linux 中文维基 (archlinuxcn.org)。
因为用了 modprobe,所以 vfio 也能正常工作了,上面说的bind文件也能找到了。
$ lsmod | grep vfio
输出如下:
vfio_pci163840vfio_pci_core942081 vfio_pcivfio_iommu_type1491520vfio614403 vfio_pci_core,vfio_iommu_type1,vfio_pciiommufd737281 vfioirqbypass163842 vfio_pci_core,kvm
5. 安装KVM虚拟机并配置显卡:
至此显卡已经被成功虚拟化,接下来就是加载到KVM之中,KVM的安装很简单,GUI界面的安装网上有很多资料,这里留下安装命令。
$ sudo apt-get install qemu-kvm libvirt-clients libvirt-daemon-system bridge-utils virt-manager ovmf
启用libvirt服务和开机自启
sudo systemctl start libvirtd.service
sudo systemctl enable libvirtd.service
如果你装完上面的命令后不想再重启更新用户组之类的,可以直接sudo virt-manager
。
创建Win10镜像,这里就不赘述了,操作很简单,找好ISO镜像,安装即可,这里备注几个重要的点。
首先,如果这里没有自动识别ISO中的系统可以输入后自己手动选择一下,比如这里选择win10就可以了。
其次,创建成功后再把显卡的PCIE添加进来,注意Nividia的声卡也要加进来。
进入系统后安装Nvidia官方的驱动就可以在设备管理器里找到显卡了,但还需要继续配置才能使用显卡接管显示输出。
6. 高清显示与Parsec远程
parsec和其他软件都是需要启动显卡才能高清输出的,所以这里还要创建一个虚拟高分辨率显示器,创建步骤很简单,参考这个博客即可,Win10/11 如何开启 第二屏幕/副屏/虚拟显示器,让平板成为副屏 – 知乎 (zhihu.com)。
安装里面的脚本,在CMD里执行下面的命令:
deviceinstaller64 install usbmmidd.inf usbmmidd
deviceinstaller64 enableidd 1 # 创建屏幕
deviceinstaller64 enableidd 0 # 用来关闭屏幕
完成后就能在windows的设立里看到第二个屏幕可用了,再把第二块屏的分辨率调到3840×2160(16:9)的4k,并设置为主屏。
最后打开parsec之类的软件就能够正常高清输出了,当然parsec需要网络能够满足p2p的环境,如果连接不了报错,可以在B站搜索相关错误代码,确认自己的情况,我这里尝试了同一个路由内虚拟机默认的网络配置是可以连接的,但设备在不同路由下面的时候会跨网段,虚拟机就不能建立p2p连接了(例如宿主机在172.19.1.10,虚拟机在宿主机中但没有相关172.19的网段,测试用的笔记本在172.20.1.20),可能的思路是需要修改虚拟机的网卡让其转到172.19网段下面再尝试。
ToDesk里也会显示当前是否为p2p的连接,可以利用ToDesk来辅助检测网络环境。