动态核显直通
-
环境介绍:我使用的是搭载Intel 12100 CPU、搭载UHD 730核显,底层运行的是内核6.5.11的8.2.4版本PVE 。文件管理由群晖(Synology)负责,而媒体服务器则由LXC容器中的Jellyfin担任,它利用核显进行硬解。系统上还运行着Windows 10、Home Assistant(HA)、OpenWRT(OP)和Ubuntu等。此外,我尝试了新推出的FNOS,简单测试下来发现还不错打算给到更多的虚拟机资源。
-
需求分析:SRIOV技术原本是为了实现一卡多用,即允许单个物理显卡被多个虚拟机共享使用。然而,实际测试后发现,尽管SRIOV能够实现核显的虚拟化与拆分实现一卡多用,但性能拉垮,因此我决定不采用这一方案。我需要在启用FNOS时,将核显动态分配给FNOS使用,而在FNOS关闭后,核显应自动返回至宿主机,以便LXC中的Jellyfin继续使用核显进行硬件解码。为了解决这一问题,我考虑使用钩子脚本,以实现核显的动态直通。
-
实现目标:我的目标是实现核显的灵活分配。首先,确保在PVE环境中,LXC容器中的Jellyfin能够调用核显进行硬解。其次,当启动FNOS时,核显应自动直通给FNOS使用。FNOS关闭后,核显应自动返回至宿主机,以便Jellyfin继续使用。
-
步骤解释:这里我们分两步,第一步自然是在虚拟机开机时把核显直通给虚拟机。第二步则是虚拟机关机时把核显返回给宿主机。
核显直通
启用内核 IOMMU 支持
- 编辑
naon /etc/default/grub
,内容如下,如果之前添加过video=efifb:off,vesafb:off
则应该删除,因为我们需要保留PVE宿主机的显示
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on"
参数 | 释义 |
---|---|
intel_iommu=on | 用 intel_iommu 来驱动 IOMMU 硬件单元 |
efifb:off | 禁用efi启动的显示设备 |
vesafb:off | 禁用legacy启动的显示设备 |
- 保存并更新 GRUB 配置
update-grub
检查并移除模块黑名单
如果你之前没有做过核显直通或者此文件里面为空可以不用管。如果有以下内容请移除模块(驱动)黑名单,即让GPU设备在下次PVE启动后允许使用这些驱动;
编辑 /etc/modprobe.d/pve-blacklist.conf
,删除如下内容。
blacklist i915
blacklist snd_hda_intel
blacklist snd_hda_codec_hdmi
options vfio_iommu_type1 allow_unsafe_interrupts=1
参数 | 释义 |
---|---|
blacklist i915 | 屏蔽显卡驱动 |
blacklist snd_hda_intel | 屏蔽板载音频设备 |
blacklist snd_hda_codec_hdmi | 屏蔽hdmi音频设备 |
options vfio_iommu_type1 allow_unsafe_interrupts=1 | 允许不安全的设备中断 |
加载内核模块
编辑内核模块加载文件/etc/modules
,追加以下内容
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
检查vfio文件
- 如果之前核显直通将核显和声卡添加到vfio文件,此时请删除文件。
rm /etc/modprobe.d/vfio.conf
更新
- 更新内核
update-initramfs -u -k all
- 更新设备id
update-pciids
- 重启
reboot
- pve中虚拟机添加PCI设备
- 检查核显是否直通过去了
核显返回
完成核显直通后我们可以通过PVE强大的钩子脚本来实现虚拟机关机后直通设备返回PVE宿主机。
下载脚本
以下项目代码来源于hellozhing大佬,克隆相关代码仓库到/root
目录,gitee和github二选一。也可以下载完成后上传到root
目录下。附上大佬原文地址:PVE虚拟机核显直通及返回钩子脚本
git clone https://gitee.com/hellozhing/pvevm-hooks.git
git clone https://github.com/HelloZhing/pvevm-hooks.git
脚本解释
名称 | 释义 |
---|---|
hooks-igpupt.pl | 根据PVE钩子脚本模板修改而来,主要内容为启动VM时调用vm-start.sh脚本,关闭虚拟机时调用vm-stop.sh脚本 |
vfio-startup.sh | PVE安装图形界面后需要用到,实现开启虚拟机PVE图形界面切换到虚拟机画面。只是动态核显直通可以删除,若需要使用需要去vm-start.sh中将#$(dirname $0)/vfio-startup.sh注释放开 |
vfio-teardown.sh | PVE安装图形界面后需要用到,实现关闭虚拟机画面切换到PVE图形界面。只是动态核显直通可以删除,若需要使用需要去vm-stop.sh中将#$(dirname $0)/vfio-teardown.sh注释放开 |
vm-start.sh | 虚拟机启动将核显绑定到虚拟机 |
vm-stop.sh | 虚拟机关机时将核显从虚拟机卸载并绑定到pve宿主机 |
添加可执行权限
cd pvevm-hooks
chmod a+x *.sh *.pl
复制perl脚本至snippets目录
mkdir /var/lib/vz/snippets
cp hooks-igpupt.pl /var/lib/vz/snippets/hooks-igpupt.pl
将钩子脚本应用到虚拟机
替换为你需要动态核显直通、返回的虚拟机ID
qm set <VMID> --hookscript local:snippets/hooks-igpupt.pl
检查钩子脚本
此时在PVE中虚拟机的选项菜单末尾出现了钩子脚本。(就是设置虚拟机开机自启、调整引导顺序那里)。
部分使用代码参考
vm-start.sh
#!/bin/bash
VMID="$1"
igd_id="8086 $(lspci -n|grep '0:02.0'|cut -d ':' -f4|cut -c 1-4)"
echo "VM $VMID is starting" > $(dirname $0)/$VMID-hooks.log
#$(dirname $0)/vfio-startup.sh
sleep 1
echo 0000:00:02.0 > /sys/bus/pci/drivers/i915/unbind
if ! lsmod | grep "vfio_pci" &> /dev/null ; then
modprobe vfio-pci
fi
echo $igd_id > /sys/bus/pci/drivers/vfio-pci/new_id
vm-stop.sh
#!/bin/bash
VMID="$1"
ia_addr="0000:$(lspci|grep 'Audio'|grep 'Intel'|cut -c 1-7)"
usb_addr="0000:$(lspci|grep 'USB'|grep 'Intel'|cut -c 1-7)"
igd_id="8086 $(lspci -n|grep '0:02.0'|cut -d ':' -f4|cut -c 1-4)"
echo "waitting" >> $(dirname $0)/$VMID-hooks.log
sleep 10
TimeSec=0
until ! test -e "/var/run/qemu-server/$VMID.pid"
do
if [ $[$TimeSec%3600] -eq 0 ]; then
echo "VM $VMID is running "$(date "+%Y-%m-%d %H:%M:%S") >> $(dirname $0)/$VMID-hooks.log
fi
sleep 3
let TimeSec+=3
done
#卸载vfio中的核显驱动
echo 0000:00:02.0 > /sys/bus/pci/drivers/vfio-pci/unbind
echo $igd_id > /sys/bus/pci/drivers/vfio-pci/remove_id
#绑定显卡驱动到pve
echo 0000:00:02.0 > /sys/bus/pci/drivers/i915/bind
#卸载vfio中的声卡驱动
#echo $ia_addr > /sys/bus/pci/drivers/vfio-pci/unbind
#绑定声卡驱动到pve
#echo $ia_addr > /sys/bus/pci/drivers/snd_hda_intel/bind
#卸载vfio中的usb控制器
#echo $usb_addr > /sys/bus/pci/drivers/vfio-pci/unbind
#绑定USB控制器到PVE
#echo $usb_addr >/sys/bus/pci/drivers/xhci_hcd/bind
sleep 1
#$(dirname $0)/vfio-teardown.sh
echo "VM $VMID stopped "$(date "+%Y-%m-%d %H:%M:%S") >> $(dirname $0)/$VMID-hooks.log