Intel GVT-g

出自 ArchWiki

Intel GVT-g 是一项为 Intel GPU(Broadwell 及更新型号)提供中介设备直通的技术。它可以用于为多个客户虚拟机虚拟化 GPU,从而在虚拟机中有效地提供接近原生的图形性能,同时仍然允许您的主机正常使用虚拟化的 GPU。如果您希望在没有专用 GPU 的超极本上运行的 Windows 虚拟机中获得加速图形,而无需 完全设备直通,这将非常有用。(NVIDIA 和 AMD GPU 也存在类似的技术,但它们仅在“专业”GPU 产品线中提供,如 Quadro、Radeon Pro 等。)

还有一种称为 GVT-d 的技术变体 - 它本质上是 Intel 对使用 vfio-pci 驱动程序的完全设备直通的称呼。使用 GVT-d,主机无法使用虚拟化的 GPU。

先决条件

Intel GVT-g 目前仅适用于 Intel Broadwell(第 5 代)到 Comet Lake(第 10 代),这是因为 i915 驱动程序缺少对 Ice Lake(第 10 代移动处理器)、Rocket Lake(第 11 代桌面处理器)及更新型号的支持。有关详细信息,请参阅此 Intel 支持帖子 和此 Github Issue

目前,Ice Lake 仅支持 GVT-d。对于基于 Xe 架构 (Gen12) 的 GPU,则需要 SR-IOV 功能。有关更多详细信息,请参阅 QEMU/Guest graphics acceleration#SR-IOV

您必须先创建一个虚拟 GPU,然后将其分配给您的虚拟机。带有虚拟 GPU 的客户机将其视为“常规”GPU - 只需安装最新的原生驱动程序即可。(虚拟 GPU 实际上确实需要专门的驱动程序才能正常工作,但最新的上游 Linux/Windows 驱动程序中已存在所有必需的更改。)

您将需要:

  • 使用至少 Linux 4.16 和 QEMU 2.12。
  • 通过将 intel_iommu=on 添加到您的 内核参数 来启用 IOMMU。
  • 启用 内核模块:kvmgtvfio-iommu-type1mdev
  • 设置 i915 内核模块参数 enable_gvt=1 以启用 GPU 虚拟化。
  • i915.enable_guc=0 添加到 内核参数,请参阅 Intel graphics#Enable GuC / HuC firmware loading 中的警告。
  • 通过运行 lspci -D -nn 找到您的 GPU 的 PCI 地址 $GVT_PCI(例如 0000:00:02.0)。
  • 生成一个虚拟 GPU GUID(以下命令中的 $GVT_GUID),您将使用它来创建和分配虚拟 GPU。单个虚拟 GPU 只能分配给单个虚拟机 - 根据您想要的虚拟 GPU 数量创建尽可能多的 GUID。(您可以通过运行 uuidgen 来做到这一点。)

使用 i915.enable_gvt=1 标志重新启动后,您应该能够创建虚拟 GPU。

您可以像这样列出可以在您的系统上创建的虚拟 GPU 类型

$ ls /sys/devices/pci0000\:00/$GVT_PCI/mdev_supported_types

编号较小的类型 能够支持更高的分辨率、更大的视频内存分配,并且能够使用更多的 GPU 时间片。

可以像这样访问每种类型的功能描述

$ cat /sys/devices/pci0000\:00/$GVT_PCI/mdev_supported_types/$GVT_TYPE/description
注意: 如果目录存在但内容为空,您可以尝试在计算机固件中增加 AGP aperture 大小。

选择您想要使用的类型 - 我们将在下面将其称为 $GVT_TYPE

使用您创建的 GUID 创建具有所选类型的虚拟 GPU

# echo "$GVT_GUID" > "/sys/devices/pci0000\:00/$GVT_PCI/mdev_supported_types/$GVT_TYPE/create"

您可以根据需要使用不同的 GUID 重复此操作多次。所有创建的虚拟 GPU 都将位于 /sys/bus/pci/devices/$GVT_PCI/ 中 - 如果您想删除虚拟 GPU,您可以执行

# echo 1 > /sys/devices/pci0000\:00/$GVT_PCI/$GVT_GUID/remove

libvirt QEMU 钩子

使用 libvirt,可以libvirt QEMU 钩子 在机器启动时自动创建虚拟 GPU,并在机器停止时将其删除。将变量替换为您在上面找到的值,并将 DOMAIN 替换为机器的名称。

/etc/libvirt/hooks/qemu
#!/bin/sh
GVT_PCI=<GVT_PCI>
GVT_GUID=<GVT_GUID>
MDEV_TYPE=<GVT_TYPE>
DOMAIN=<DOMAIN name>
if [ $# -ge 3 ]; then
    if [[ " $DOMAIN " =~ .*\ $1\ .* ]] && [ "$2" = "prepare" ] && [ "$3" = "begin" ]; then
        echo "$GVT_GUID" > "/sys/devices/pci0000:00/$GVT_PCI/mdev_supported_types/$MDEV_TYPE/create"
    elif [[ " $DOMAIN " =~ .*\ $1\ .* ]] && [ "$2" = "release" ] && [ "$3" = "end" ]; then
        echo 1 > "/sys/devices/pci0000:00/$GVT_PCI/$GVT_GUID/remove"
    fi
fi

不要忘记使文件 可执行,并引用每个变量值,例如 GVT_PCI="0000:00:02.0"。您还需要重新启动 libvirtd 守护程序,以便它知道新的钩子。

注意
  • 如果您使用 libvirt 用户会话,您需要调整脚本以使用 权限提升 命令,例如 pkexec(1) 或无密码 sudo
  • 域的 XML 通过 stdin 馈送到钩子脚本。您可以使用 xmllint 和 XPath 表达式从 stdin 中提取 GVT_GUID,例如
    GVT_GUID="$(xmllint --xpath 'string(/domain/devices/hostdev[@type="mdev"][@display="on"]/source/address/@uuid)' -)"

启动时 systemd 服务

除了 QEMU 钩子之外,您还可以让 systemd 在启动时创建虚拟 GPU。只要虚拟机不使用虚拟 GPU,这就不依赖于 libvirt,并且似乎对主机上的 GPU 性能没有影响。

创建一个 bash 脚本,并将 echo 命令放在 先决条件 中确定的位置以创建虚拟 GPU。使其 可执行。确保非特权用户无法修改该脚本,因为它将在启动时以 root 身份运行。

现在 创建一个 systemd 服务 来运行该脚本,并为其提供以下属性

After=graphical.target
Type=oneshot
User=root

为虚拟机分配虚拟 GPU

如果您以普通用户身份运行 qemulibvirtd,它可能会抱怨某些路径 /dev/vfio/number 不可写。您需要使用 chmodsetfacl 为该帐户启用对该路径的写入访问权限。

QEMU CLI

要创建具有虚拟化 GPU 的虚拟机,请将此参数添加到 QEMU 命令行

-device vfio-pci,sysfsdev=/sys/bus/mdev/devices/$GVT_GUID
注意: 必须通过 -enable-kvm 启用 KVM。

libvirt

将以下设备添加到虚拟机定义的 devices 元素中

$ virsh edit vmname
...
    <hostdev mode='subsystem' type='mdev' managed='no' model='vfio-pci' display='off'>
      <source>
        <address uuid=GVT_GUID/>
      </source>
    </hostdev>
...

GVT_GUID 替换为您的虚拟 GPU 的 UUID。

获取虚拟 GPU 显示内容

有几种可能的方法可以从虚拟 GPU 检索显示内容。

使用 DMA-BUF 显示

警告: 根据此 issue,此方法不适用于使用(未修改的)OVMF 的 UEFI 客户机。使用基于 BIOS 的客户机(例如使用 SeaBIOS)或参阅下面的补丁/解决方法。

QEMU CLI

display=on,x-igd-opregion=on 添加到 -device vfio-pci 参数的末尾,例如

-device vfio-pci,sysfsdev=/sys/bus/mdev/devices/$GVT_GUID,display=on,x-igd-opregion=on

libvirt

首先,修改虚拟机定义的 XML 架构,以便我们可以稍后使用 QEMU 特定的元素。更改

$ virsh edit vmname
<domain type='kvm'>

$ virsh edit vmname
<domain xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0' type='kvm'>

然后将此配置添加到 <domain> 元素的末尾,即,将此文本插入到结束标记 </domain> 的正上方

$ virsh edit vmname
...
  <qemu:override>
    <qemu:device alias="hostdev0">
      <qemu:frontend>
        ...
        <qemu:property name="x-igd-opregion" type="bool" value="true"/>
      </qemu:frontend>
    </qemu:device>
  </qemu:override>
...

将 DMA-BUF 与 UEFI/OVMF 结合使用

如上所述,DMA-BUF 显示不适用于使用(未修改的)OVMF 的基于 UEFI 的客户机,因为它不会创建必要的 ACPI OpRegion,该 OpRegion 通过 QEMU 的非标准 fw_cfg 接口公开。有关此问题的详细信息,请参阅 此 OVMF 错误[死链接 2025-01-19 ⓘ]

根据 此 GitHub 评论,OVMF 错误报告提出了几个解决问题的方案。可以:

我们将选择最后一个选项,因为它不涉及任何修补。(注意:如果链接 存档 都失效,则可以手动从内核补丁中提取 OpROM。)

i915ovmfAUR 可用,它是已存档的 i915ovmfPkg最新分支,它是当前用于 OVMF 的最先进的 GVT-g ROM,请参阅 此讨论。AUR 软件包将 UEFI rom 文件安装为 /var/lib/libvirt/qemu/drivers/i915ovmf.rom 。首次启动可能需要一段时间,尤其是当客户机操作系统需要安装驱动程序时,Windows 10 就是这种情况。

下载 vbios_gvt_uefi.rom 并将其放置在世界可访问的位置(我们将使用 / 作为示例)。

注意
  • 如果使用 SELinux,它将拒绝访问 .rom 文件,从而导致 QEMU 无法找到它的消息。
  • 要安全地授予对 .rom 文件的访问权限,而无需禁用 SELinux 或切换到被动模式,请将 vbios_gvt_uefi.rom 文件放在 /var/lib/libvirt 中,然后以 root 身份运行 restorecon -Rv /var/lib/libvirt。然后 SELinux 将应用正确的标签,从而授予对 QEMU 的 svirt_t 策略的访问权限。

QEMU CLI

要指定 vBIOS ROM 文件,请将 ,romfile=/path/to/vbios_gvt_uefi.rom 附加到 -device vfio-pci,...

libvirt

然后编辑虚拟机定义,将此配置附加到我们之前添加的 <qemu:override> 元素

$ virsh edit vmname
...
  <qemu:override>
    <qemu:device alias="hostdev0">
      <qemu:frontend>
      ...
        <qemu:property name="romfile" type="string" value="/path/to/vbios_gvt_uefi.rom"/>
      ...
      </qemu:frontend>
    </qemu:device>
  </qemu:override>
...

启用 RAMFB 显示(可选)

这应与上述 DMA-BUF 配置结合使用,以便也显示在客户机 Intel 驱动程序加载之前发生的所有事情(即 POST、固件界面和客户机初始化)。

QEMU CLI

ramfb=on,driver=vfio-pci-nohotplug 添加到 -device vfio-pci 参数的末尾,例如

-device vfio-pci,sysfsdev=/sys/bus/mdev/devices/$GVT_GUID,display=on,x-igd-opregion=on,ramfb=on,driver=vfio-pci-nohotplug

libvirt

首先,按照 本节 的第一步修改 XML 架构。

然后将此配置添加到 <domain> 元素的末尾,即,将此文本插入到结束标记 </domain> 的正上方

$ virsh edit vmname
...
  <qemu:override>
    <qemu:device alias="hostdev0">
      <qemu:frontend>
        ...
        <qemu:property name="driver" type="string" value="vfio-pci-nohotplug"/>
        <qemu:property name="ramfb" type="bool" value="true"/>
      </qemu:frontend>
    </qemu:device>
  </qemu:override>
...

显示虚拟 GPU 输出

由于 spice-gtk 的 问题,配置因 SPICE 客户端 EGL 实现而异。

使用 QEMU GTK 显示输出

至少对于 Windows 客户机而言,此方法将为您提供比弱 CPU 上的 SPICE 显示更高的刷新率和更少的延迟/输入延迟。此外,它的 CPU 密集程度低于 Looking Glass。但是您会失去有用的 SPICE 显示功能,例如:

  • 共享剪贴板(可以重新获得,请参阅 QEMU#qemu-vdagent
  • 实时 USB 重定向(您需要在启动客户机之前分配 USB 设备)
  • 鼠标光标可以自由进出虚拟机(将被捕获,除非您使用 USB 平板电脑输入设备
  • 将显示输出集成到 virt-manager 中(将为 GTK 显示生成单独的窗口)

只有当客户机启动其正确的 Intel GPU 驱动程序(通常在登录屏幕上)时,显示输出才会开始工作。这意味着:

  • 最好预先安装 Intel GPU 驱动程序,或使用不同的虚拟显示适配器(如 -vga stdlibvirt)与 Intel vGPU 一起安装 Intel GPU 驱动程序,然后删除 std 视频适配器。
  • 您永远不会看到操作系统启动,如果它在登录之前崩溃,您需要切换到不同的虚拟显示适配器。
  • 如果您需要访问 BIOS,则需要 启用 RAMFB 显示
提示: 在 QEMU GTK 中,Ctrl+Alt+G 捕获或释放鼠标光标,Ctrl+Alt+F 在窗口和全屏模式之间切换。

QEMU CLI

-display gtk,gl=on 添加到命令行。可以通过添加 -vga none 来禁用 QEMU VGA 适配器,或者您有两个虚拟屏幕,连接到 QEMU VGA 适配器的那个屏幕是空白的。

libvirt

  • 确保上面添加的 <hostdev> 设备已将 display 属性设置为 'off'
  • 确保您已将虚拟行 xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0' 添加到您的 domain(来自步骤 使用 DMA-BUF 显示)。
  • 删除所有 <graphics><video> 设备。

QEMU GTK 显示窗口需要被告知它应该使用哪个显示输出运行 OpenGL。在笔记本电脑上,首先断开所有外部显示器,以便您只有一个笔记本电脑屏幕作为显示器。通过粘贴到终端来获取 GPU 输出到的显示器的编号:echo $DISPLAY。一个示例是 :0。您现在可以重新连接您之前使用的任何显示器。将您刚刚确定的数字插入到下面的 env name='DISPLAY' 行中。

  • 添加以下 QEMU 命令行参数
$ virsh edit vmname
...
  <qemu:commandline>
    <qemu:arg value="-display"/>
    <qemu:arg value="gtk,gl=on,zoom-to-fit=off"/>
    <qemu:env name="DISPLAY" value=":0"/>
  </qemu:commandline>
  <qemu:override>
    <qemu:device alias="hostdev0">
      <qemu:frontend>
        <qemu:property name="display" type="string" value="on"/>
        ...
      </qemu:frontend>
    </qemu:device>
  </qemu:override>
...

缩放

在窗口模式下,-display gtk,gl=on,zoom-to-fit=off 使 GTK 显示窗口大小与客户机显示的分辨率一样大,这为您提供 1:1 像素纵横比。打开此选项或将其忽略会使客户机显示拉伸/收缩到 GTK 窗口的实际大小(难看的缩放)。

在全屏模式下,无论如何您都会获得缩放,并且当您更改客户机分辨率时,缩放仅在降低分辨率时更新。当您增加分辨率时,图像会变得比您的显示器更大,因此您需要退出全屏并再次进入。

GTK 显示 CPU 负载

这是将客户机帧缓冲区复制到 GTK 显示窗口的方法之间的权衡。

gl=es 代替 gl=on 会将复制客户机帧缓冲区的任务委托给 GPU(通过 DMA)。

这降低了 GTK 显示的 CPU 负载,并且可以产生响应更快的客户机,尤其是在弱 CPU 上。

当此帧缓冲区复制操作与加载 GPU 的应用程序共享 GPU 资源时,显示的 FPS 可能会下降,并且很可能发生卡顿(显示的帧之间的时间间隔不规则)。

使用 SPICE 与 MESA EGL 输出

QEMU CLI

-display spice-app,gl=on 添加到命令行。必须安装 virt-viewer

libvirt

  1. 确保上面添加的 <hostdev> 设备已将 display 属性设置为 'on'
  2. 删除所有 <graphics><video> 设备。
  3. 添加以下设备
$ virsh edit vmname
...
    <graphics type='spice'>
      <listen type='none'/>
      <gl enable='yes'/>
    </graphics>
    <video>
      <model type='none'/>
    </video>
...

gl 标记中有一个可选属性 rendernode,用于允许指定渲染器,例如

<gl enable='yes' rendernode='/dev/dri/by-path/pci-0000:00:02.0-render'/>

使用 SPICE 与 NVIDIA EGL 或 VNC 输出

libvirt

  1. 确保上面添加的 <hostdev> 设备已将 display 属性设置为 'on'。
  2. 删除所有 <graphics> 和 <video> 设备。
  3. 添加以下设备
$ virsh edit vmname
...
    <graphics type='spice' autoport='yes'>
      <listen type='address'/>
    </graphics>
    <graphics type='egl-headless'/>
    <video>
      <model type='none'/>
    </video>
...

可以将 <graphics type='spice'> 类型更改为 'vnc' 以使用 VNC 代替。

此外,在 <graphics type='egl-headless'> 标记内有一个可选标记 <gl>,用于强制使用特定的渲染器,不要将其放在 'spice' 图形中,因为提到了错误,例如

<graphics type='egl-headless'>
  <gl rendernode='/dev/dri/by-path/pci-0000:00:02.0-render'/>
</graphics>

禁用所有输出

如果禁用所有输出,则查看显示输出的唯一方法是使用 RDP、VNC 或 Looking Glass 等软件服务器。有关详细信息,请参阅 PCI passthrough via OVMF#Using Looking Glass to stream guest screen to the host

QEMU CLI

-device vfio-pci 参数中,删除 ramfb=on 并更改为 display=off。添加 -vga none 以禁用 QEMU VGA 适配器。

libvirt

为了确保不添加模拟 GPU,可以编辑虚拟机配置并进行以下更改

  1. 删除所有 <graphics> 设备。
  2. 将 <video> 设备的类型更改为 'none'。
  3. 确保上面添加的 <hostdev> 设备已将 display 属性设置为 'off'。

故障排除

缺少 mdev_supported_types 目录

如果您已按照说明添加了 i915.enable_gvt=1 内核参数,但仍然没有 /sys/bus/pci/devices/0000:02:00.0/mdev_supported_types 目录,请首先仔细检查是否已加载 kvmgt 模块。

您还应该检查您的硬件是否受支持。检查 dmesg 的输出以查找此消息

# dmesg | grep -i gvt 
[    4.227468] [drm] Unsupported device. GVT-g is disabled

如果是这种情况,您可能需要向上游检查支持计划。例如,对于“Coffee Lake”(CFL) 平台的支持,请参阅 https://github.com/intel/gvt-linux/issues/53

Windows 挂起并出现错误内存错误

如果 Windows 由于错误内存错误而挂起,请通过 dmesg 查找更多详细信息。如果主机内核日志显示类似 rlimit memory exceeded 的内容,您可能需要增加 Linux 允许 QEMU 分配的最大内存。假设您在 kvm 组中,请将以下内容添加到 /etc/security/limits.d/42-intel-gvtg.conf 并重新启动系统。

# qemu kvm, need high memlock to allocate memory for vga-passthrough
@kvm - memlock 8388608

将 Intel GVT-G 与 PRIME render offload 结合使用

在主机上使用 Intel GVT-G 的同时使用 NVIDIA 的 PRIME render offload 会在客户机上导致一些 问题。建议使用 bbswitch 来保持显卡断电,或将其与 Bumblebeenvidia-xrunoptimus-manager 结合使用。

无显示

如果您的虚拟机在使用 RAMFB 显示时未显示任何内容,请尝试将以下附加选项添加到现有的 <qemu:commandline> 标记

$ virsh edit vmname
...
  <qemu:commandline>
    <qemu:arg value="-set"/>
    <qemu:arg value="device.hostdev0.display=on"/>
  </qemu:commandline>
...
注意: 截至 QEMU 8.1.0,据报告,带有 OpenGL 的 qemu-ui-gtk 对于 GVT-g 来说已损坏,导致客户机驱动程序初始化后出现黑色客户机显示,并且如果直接运行 qemu(不带 libvirt),则终端会打印消息 qemu: eglCreateImageKHR failed降级 到 8.0.4 以进行临时修复。

图形错乱

如果您的虚拟机在鼠标进入虚拟机屏幕时显示伪影,则以下 解决方法 可能会奏效。

首先,按照 #libvirt 2 上所示修改 XML 架构。

然后,将此插入到结束标记 </domain> 的正上方,注意添加到现有的 <qemu:commandline> 标记(如果存在)

$ virsh edit vmname
...
  <qemu:commandline>
    <qemu:env name="MESA_LOADER_DRIVER_OVERRIDE" value="i965"/>
  </qemu:commandline>
...

尝试挂起时主机挂起

创建 GVT-g 虚拟 GPU 后,主机在尝试挂起时可能会挂起。请参阅 github 以跟踪此错误。

一种解决方法是在挂起之前删除创建的 GVT-g 虚拟 GPU,并在从挂起唤醒后重新创建 GVT-g 虚拟 GPU。您可以安装 gvtg_vgpu-gitAUR 软件包来自动为您执行此操作。

更改虚拟 GPU 的显示分辨率

默认情况下,vGPU 的显示分辨率是 vGPU 能够达到的最大分辨率。无论客户机操作系统设置什么分辨率,vGPU 都会将显示内容缩放到此分辨率。这将在查看器中产生质量较差的图片。

要更改显示分辨率,请将 xresyres 配置附加到 <qemu:override> 元素中

$ virsh edit vmname
...
  <qemu:override>
    <qemu:device alias="hostdev0">
      <qemu:frontend>
        ...
        <qemu:property name="xres" type="unsigned" value="800"/>
        <qemu:property name="yres" type="unsigned" value="600"/>
        ...
      </qemu:frontend>
    </qemu:device>
  </qemu:override>
...

参见