通过 OVMF 进行 PCI 直通
Open Virtual Machine Firmware (OVMF) 是一个为虚拟机启用 UEFI 支持的项目。从 Linux 3.9 和最新版本的 QEMU 开始,现在可以直通显卡,为虚拟机提供近乎原生的图形性能,这对于图形密集型任务非常有用。
前提是您拥有一台带有可用 GPU 的台式机(无论是集成 GPU 还是旧的 OEM 卡,品牌甚至不必匹配),并且您的硬件支持它(请参见 #Prerequisites),那么就可以拥有一个运行任何操作系统的虚拟机,并为其配备专用的 GPU 和近乎原生的性能。有关技术的更多信息,请参阅背景 演示文稿 (pdf)。
前提条件
VGA 直通依赖于许多目前并非普遍存在且可能不在您的硬件上可用的技术。除非满足以下要求,否则您将无法在您的机器上执行此操作
- 您的 CPU 必须支持硬件虚拟化 (kvm) 和 IOMMU (用于直通本身)。
- 兼容的 Intel CPU 列表 (Intel VT-x 和 Intel VT-d).
- 从 Bulldozer 系列及以上 (包括 Zen) 的 AMD CPU 都应该是兼容的。
- 您的主板也必须支持 IOMMU。
- 芯片组和 BIOS 都必须支持。并非总是能一眼看出是否如此,但在 Xen wiki 以及 Wikipedia:List of IOMMU-supporting hardware 上有一个相当全面的列表。
- 您的客户机 GPU ROM 必须支持 UEFI。
- 如果您能在此列表中找到适用于您特定 GPU 并且据称支持 UEFI 的 任何 ROM,那么您通常就没问题了。2012 年及之后的 GPU 都应支持此功能,因为微软要求 UEFI 是设备兼容 Windows 8 的先决条件。
您可能需要一个备用显示器,或者一个带有多个输入端口的显示器连接到不同的 GPU(直通 GPU 在没有连接屏幕时不会显示任何内容,并且使用 VNC 或 Spice 连接也无助于提高性能),以及一个可以传递给虚拟机的鼠标和键盘。如果出现任何问题,至少您还可以通过这种方式控制您的宿主机。
设置 IOMMU
- IOMMU 是 Intel VT-d 和 AMD-Vi 的通用名称。
- VT-d 代表 _Intel Virtualization Technology for Directed I/O_,不应与 VT-x _Intel Virtualization Technology_ 混淆。VT-x 允许一个硬件平台充当多个“虚拟”平台,而 VT-d 提高了系统的安全性和可靠性,并且还提高了虚拟化环境中 I/O 设备的性能。
使用 IOMMU 可以实现 PCI 直通和对有故障或恶意设备的内存保护等功能,请参阅 Wikipedia:Input-output memory management unit#Advantages 和 Memory Management (computer programming): Could you explain IOMMU in plain English?。
启用 IOMMU
确保您的 CPU 支持 AMD-Vi 或 Intel VT-d,并在 BIOS 设置中启用。这些选项通常出现在其他 CPU 功能旁边,可能位于与超频相关的菜单中。它们可能以其真实名称(“VT-d”或“AMD-Vi”)列出,或者以更模糊的术语(如“Virtualization Technology”)列出,这可能在主板手册中有解释,也可能没有。
使用 dmesg 检查 IOMMU 是否已启用
# dmesg | grep -i IOMMU
[ 0.000000] ACPI: DMAR 0x00000000BDCB1CB0 0000B8 (v01 INTEL BDW 00000001 INTL 00000001) [ 0.000000] Intel-IOMMU: enabled [ 0.028879] dmar: IOMMU 0: reg_base_addr fed90000 ver 1:0 cap c0000020660462 ecap f0101a [ 0.028883] dmar: IOMMU 1: reg_base_addr fed91000 ver 1:0 cap d2008c20660462 ecap f010da [ 0.028950] IOAPIC id 8 under DRHD base 0xfed91000 IOMMU 1 [ 0.536212] DMAR: No ATSR found [ 0.536229] IOMMU 0 0xfed90000: using Queued invalidation [ 0.536230] IOMMU 1 0xfed91000: using Queued invalidation [ 0.536231] IOMMU: Setting RMRR: [ 0.536241] IOMMU: Setting identity map for device 0000:00:02.0 [0xbf000000 - 0xcf1fffff] [ 0.537490] IOMMU: Setting identity map for device 0000:00:14.0 [0xbdea8000 - 0xbdeb6fff] [ 0.537512] IOMMU: Setting identity map for device 0000:00:1a.0 [0xbdea8000 - 0xbdeb6fff] [ 0.537530] IOMMU: Setting identity map for device 0000:00:1d.0 [0xbdea8000 - 0xbdeb6fff] [ 0.537543] IOMMU: Prepare 0-16MiB unity mapping for LPC [ 0.537549] IOMMU: Setting identity map for device 0000:00:1f.0 [0x0 - 0xffffff] [ 2.182790] [drm] DMAR active, disabling use of stolen memory
要手动启用 IOMMU 支持,请根据 CPU 类型设置正确的 内核参数
- 对于 Intel CPU (VT-d),请设置 `intel_iommu=on`,除非您的内核设置了 `CONFIG_INTEL_IOMMU_DEFAULT_ON` 配置选项。
- 对于 AMD CPU (AMD-Vi),如果内核从 BIOS 检测到 IOMMU 硬件支持,则 IOMMU 支持会自动启用。
确保组是有效的
以下脚本应允许您查看各种 PCI 设备如何映射到 IOMMU 组。如果它没有返回任何内容,则表示您尚未正确启用 IOMMU 支持,或者您的硬件不支持。
#!/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;
示例输出
IOMMU Group 1: 00:01.0 PCI bridge: Intel Corporation Xeon E3-1200 v2/3rd Gen Core processor PCI Express Root Port [8086:0151] (rev 09) IOMMU Group 2: 00:14.0 USB controller: Intel Corporation 7 Series/C210 Series Chipset Family USB xHCI Host Controller [8086:0e31] (rev 04) IOMMU Group 4: 00:1a.0 USB controller: Intel Corporation 7 Series/C210 Series Chipset Family USB Enhanced Host Controller #2 [8086:0e2d] (rev 04) IOMMU Group 10: 00:1d.0 USB controller: Intel Corporation 7 Series/C210 Series Chipset Family USB Enhanced Host Controller #1 [8086:0e26] (rev 04) IOMMU Group 13: 06:00.0 VGA compatible controller: NVIDIA Corporation GM204 [GeForce GTX 970] [10de:13c2] (rev a1) 06:00.1 Audio device: NVIDIA Corporation GM204 High Definition Audio Controller [10de:0fbb] (rev a1)
IOMMU 组是可传递给虚拟机的最小物理设备集合。例如,在上例中,06:00.0 中的 GPU 及其 6:00.1 中的音频控制器都属于 IOMMU 组 13,只能一起传递。然而,前面的 USB 控制器有自己的组(组 2),它与 USB 扩展控制器(组 10)和后面的 USB 控制器(组 4)分开,这意味着 其中任何一个都可以传递给虚拟机而不会影响其他设备。
注意事项
将客户机 GPU 插入未隔离的基于 CPU 的 PCIe 插槽
并非所有 PCI-E 插槽都相同。大多数主板都有由 CPU 和 PCH 提供的 PCIe 插槽。根据您的 CPU,基于处理器的 PCIe 插槽可能不支持正确隔离,在这种情况下,PCI 插槽本身似乎与连接到它的设备分组在一起。
IOMMU Group 1: 00:01.0 PCI bridge: Intel Corporation Xeon E3-1200 v2/3rd Gen Core processor PCI Express Root Port (rev 09) 01:00.0 VGA compatible controller: NVIDIA Corporation GM107 [GeForce GTX 750] (rev a2) 01:00.1 Audio device: NVIDIA Corporation Device 0fbc (rev a1)
只要这里只包含您的客户机 GPU,例如上图所示,这就没问题。根据您插入其他 PCIe 插槽的内容以及它们是分配给您的 CPU 还是 PCH,您可能会发现同一组中有其他设备,这将迫使您也传递它们。如果您愿意将该组中的所有内容传递给您的虚拟机,那么您可以继续。否则,您要么需要尝试将 GPU 插入其他 PCIe 插槽(如果您有的话),看看它们是否能与其余设备隔离,要么安装 ACS override 补丁,但该补丁也有其自身的缺点。有关更多信息,请参阅 #Bypassing the IOMMU groups (ACS override patch)。
隔离 GPU
为了将设备分配给虚拟机,该设备及其共享同一 IOMMU 组的所有设备都必须用存根驱动程序或 VFIO 驱动程序替换其驱动程序,以防止宿主机与它们交互。对于大多数设备,这可以在虚拟机启动前实时完成。
然而,由于 GPU 驱动程序的大小和复杂性,它们通常不支持动态重新绑定,因此您不能简单地让宿主机上使用的 GPU 在不发生驱动程序冲突的情况下透明地传递给虚拟机。因此,通常建议在启动虚拟机之前手动绑定这些占位符驱动程序,以阻止其他驱动程序尝试声明它们。
以下部分详细介绍了如何配置 GPU,以便在启动过程的早期绑定这些占位符驱动程序,这使得该设备在虚拟机声明它或驱动程序被切换回之前处于非活动状态。考虑到它比在系统完全在线后切换驱动程序具有更少的注意事项,这是首选方法。
从 Linux 4.1 开始,内核包含 vfio-pci。这是一个 VFIO 驱动程序,意味着它起的作用与 pci-stub 相同,但它还可以一定程度上控制设备,例如在不使用时将其切换到 D3 状态。
通过设备 ID 绑定 vfio-pci
Vfio-pci 通常以 ID 定位 PCI 设备,这意味着您只需要指定您打算直通的设备的 ID。对于下面的 IOMMU 组,您需要使用 `10de:13c2` 和 `10de:0fbb` 绑定 vfio-pci,这些值将在本节的其余部分用作示例。
IOMMU Group 13: 06:00.0 VGA compatible controller: NVIDIA Corporation GM204 [GeForce GTX 970] [10de:13c2] (rev a1) 06:00.1 Audio device: NVIDIA Corporation GM204 High Definition Audio Controller [10de:0fbb] (rev a1)
- 如果宿主机 GPU 和客户机 GPU 共享相同的 ID 对(即它们是相同的型号),则无法使用供应商-设备 ID 对来指定要隔离的设备。如果是这种情况,请改阅 #Using identical guest and host GPUs。
- 正如 #Plugging your guest GPU in an unisolated CPU-based PCIe slot 中所述,如果您的 PCI 根端口是您 IOMMU 组的一部分,您**不应**将其 ID 传递给 `vfio-pci`,因为它需要保持连接到宿主机才能正常工作。然而,该组中的任何其他设备都应该留给 `vfio-pci` 来绑定。
- 绑定音频设备(上例中的 `10de:0fbb`)是可选的。Libvirt 能够自行将其从 `snd_hda_intel` 驱动程序中解绑。
通过 `vfio-pci` 的 内核模块参数 `ids=10de:13c2,10de:0fbb` 提供设备 ID。
如果希望将 HDAudio 保留在宿主机上,可以使用内核模块参数 `gpu_bind=0`(针对 `snd-hda-core`)和 `enable_acomp=n`(针对 `snd-hda-codec-hdmi`)来分离它。
您可以在没有内核 `vfio-pci` ID 的情况下使用此 bash 脚本。调用 `unbind_vfio` 后,GPU 可以在宿主机上运行。
#!/bin/bash
gpu="0000:06:00.0"
aud="0000:06: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
由于 Arch 的 linux 内核将 vfio-pci 作为模块构建,我们需要强制它在图形驱动程序有机会绑定到显卡之前加载。有两种方法:modprobe 配置或将模块添加到 initramfs。
modprobe.d
此方法在 udev 加载 GPU 驱动程序时加载 `vfio`。这避免了不必要地增加 initramfs 的大小并减慢启动速度。
/etc/modprobe.d/vfio.conf
softdep drm pre: vfio-pci
如果您正在直通 NVIDIA GPU 并安装了专有的 NVIDIA 驱动程序,请改用以下方法
/etc/modprobe.d/vfio.conf
softdep nvidia pre: vfio-pci
initramfs
mkinitcpio
将 `vfio_pci`、`vfio` 和 `vfio_iommu_type1` 添加到 mkinitcpio
/etc/mkinitcpio.conf
MODULES=(... vfio_pci vfio vfio_iommu_type1 ...)
- 从内核 6.2 开始,`vfio_virqfd` 功能已合并到基础 `vfio` 模块中。
- 如果您也通过这种方式为 早期模式设置 加载了另一个驱动程序(例如 `nouveau`、`radeon`、`amdgpu`、`i915` 等),那么所有上述 VFIO 模块**必须**在其之前。
- 如果您正在为 `nvidia` 驱动程序进行模式设置,则 `vfio-pci.ids` 必须嵌入到 initramfs 镜像中。如果通过内核参数提供,则读取它们会太晚而无法生效。请按照 #Binding vfio-pci via device ID 中的说明将 ID 添加到 modprobe 配置文件中。
此外,请确保 `modconf` hook 已包含在 `mkinitcpio.conf` 的 HOOKS 列表中
/etc/mkinitcpio.conf
HOOKS=(... modconf ...)
由于已将新模块添加到 initramfs 配置中,您必须重新生成 initramfs。
booster
与 mkinitcpio 类似,您需要指定要尽早加载的模块
/etc/booster.yaml
modules_force_load: vfio_pci,vfio,vfio_iommu_type1
dracut
遵循相同的思路,我们需要确保所有 vfio 驱动程序都在 initramfs 中。将以下文件添加到 `/etc/dracut.conf.d`
10-vfio.conf
force_drivers+=" vfio_pci vfio vfio_iommu_type1 "
请注意,我们使用了 `force_drivers` 而非常规的 `add_drivers` 选项,这将确保通过 modprobe 尽早尝试加载驱动程序(参见 Dracut#Early kernel module loading)。
与 mkinitcpio 一样,之后您必须重新生成 initramfs。有关更多详细信息,请参阅 dracut。
验证配置是否成功
重新启动并验证 vfio-pci 是否已正确加载,并且它现在已绑定到正确的设备。
# dmesg | grep -i vfio
[ 0.329224] VFIO - User Level meta-driver version: 0.3 [ 0.341372] vfio_pci: add [10de:13c2[ffff:ffff]] class 0x000000/00000000 [ 0.354704] vfio_pci: add [10de:0fbb[ffff:ffff]] class 0x000000/00000000 [ 2.061326] vfio-pci 0000:06:00.0: enabling device (0100 -> 0103)
`vfio.conf` 中的所有设备(或甚至预期设备)出现在 dmesg 输出中并非必需。即使某个设备未出现,它仍可能在客户机虚拟机中可见和可用。
$ lspci -nnk -d 10de:13c2
06:00.0 VGA compatible controller: NVIDIA Corporation GM204 [GeForce GTX 970] [10de:13c2] (rev a1) Kernel driver in use: vfio-pci Kernel modules: nouveau nvidia
$ lspci -nnk -d 10de:0fbb
06:00.1 Audio device: NVIDIA Corporation GM204 High Definition Audio Controller [10de:0fbb] (rev a1) Kernel driver in use: vfio-pci Kernel modules: snd_hda_intel
设置基于 OVMF 的客户机虚拟机
OVMF 是 QEMU 虚拟机的一个开源 UEFI 固件。虽然可以使用 SeaBIOS 获得与实际 PCI 直通类似的结果,但设置过程不同,如果您的硬件支持,通常更倾向于使用 EFI 方法。
配置 libvirt
Libvirt 是一个封装了多种虚拟化工具的库,它极大地简化了虚拟机配置和部署过程。对于 KVM 和 QEMU,它提供的前端允许我们避免处理 QEMU 的权限,并使在活动虚拟机上添加和删除各种设备变得更容易。然而,作为封装库的地位意味着它可能并非总是支持所有最新的 qemu 功能,这可能需要使用包装脚本来为 QEMU 提供一些额外的参数。
安装 qemu-desktop、libvirt、edk2-ovmf 和 virt-manager。对于默认网络连接,需要 dnsmasq。
遵循 Libvirt#Configuration 来配置 libvirt 以便使用。
您可能还需要激活默认的 libvirt 网络
# virsh net-autostart default # virsh net-start default
设置客户机操作系统
使用 `virt-manager` 设置虚拟机的过程大部分是自解释的,因为过程的大部分都有相当全面的屏幕说明。
但是,您应该特别注意以下步骤
- 当虚拟机创建向导要求您为虚拟机命名时(“完成”按钮之前的最后一步),请勾选“安装前自定义”复选框。
- 在“概述”部分,将固件设置为“UEFI”。如果该选项为灰色,请确保:
- 您的 Hypervisor 以系统会话而不是用户会话的形式运行。您可以通过单击,然后将鼠标悬停在 virt-manager 中的会话上来验证这一点。如果您意外地以用户会话的形式运行它,则必须通过单击“文件”>“添加连接..”来打开新连接,然后从下拉菜单中的“QEMU/KVM”选项中选择,而不是“QEMU/KVM 用户会话”。
- 在“CPU”部分,将您的 CPU 模型更改为“host-passthrough”。如果它不在列表中,您将需要手动输入或使用 `virt-xml vmname --edit --cpu host-passthrough`。这将确保您的 CPU 被正确检测,因为它会导致 libvirt 暴露您 CPU 的全部功能,而不是仅暴露它识别的功能(这是为了使 CPU 行为更容易重现的推荐默认行为)。没有它,一些应用程序可能会抱怨您的 CPU 是未知型号。
- 如果您想最小化 IO 开销,最好在安装前设置 #Virtio disk。
安装的其余过程将正常进行,使用在窗口中运行的标准 QXL 视频适配器。此时,无需为其余的虚拟设备安装额外的驱动程序,因为其中大多数稍后会被删除。一旦客户机操作系统完成安装,只需关闭虚拟机。首次启动虚拟机时,您可能会进入 UEFI 菜单而不是开始安装。有时,系统未能自动检测到正确的 ISO 文件,您需要手动指定要引导的驱动器。通过键入 exit 并导航到“boot manager”,您将进入一个允许您在设备之间进行选择的菜单。
附加 PCI 设备
安装完成后,就可以在 libvirt 中编辑硬件详细信息,并移除虚拟集成设备,例如 spice 通道和虚拟显示器、QXL 视频适配器、模拟鼠标和键盘以及 USB 平板电脑设备。
例如,从您的 XML 文件中删除以下部分
<channel type="spicevmc">
...
</channel>
<input type="tablet" bus="usb">
...
</input>
<input type="mouse" bus="ps2"/>
<input type="keyboard" bus="ps2"/>
<graphics type="spice" autoport="yes">
...
</graphics>
<video>
<model type="qxl" .../>
...
</video>
由于这样您将没有任何输入设备,您可能还需要将一些 USB 主机设备绑定到您的虚拟机,但请记住,**至少要为主机保留一个鼠标和/或键盘**,以防客户机出现问题。这可以通过使用 Add Hardware > USB Host Device 来完成。
此时,还可以附加之前隔离的 PCI 设备;只需单击“Add Hardware”并选择您想要直通的 PCI Host Devices。如果一切顺利,连接到 GPU 的屏幕应显示 OVMF 启动屏幕,并且您的虚拟机应正常启动。从那里,您可以为虚拟机的其余部分设置驱动程序。
显卡驱动程序虚拟化检测
AMD 的显卡驱动程序包含非常基本的虚拟机检测,针对 Hyper-V 扩展。如果触发此检测机制,驱动程序将拒绝运行,导致屏幕变黑。
如果出现这种情况,则需要修改报告的 Hyper-V 供应商 ID
$ virsh edit vmname
...
<features>
...
<hyperv>
...
<vendor_id state='on' value='randomid'/>
...
</hyperv>
...
</features>
...
465 版本之前的 Nvidia 客户机驱动程序表现出类似的行为,导致在显卡设备管理器状态中出现通用的错误 43。因此,使用这些旧驱动程序的系统也需要上述修改。此外,它们还需要隐藏 KVM CPU leaf。
$ virsh edit vmname
...
<features>
...
<kvm>
<hidden state='on'/>
</kvm>
...
</features>
...
请注意,上述步骤并不等同于从 Windows 或虚拟机中运行的任何驱动程序/程序中“隐藏”虚拟机。此外,与此处提到的任何检测机制无关的各种其他问题也可能触发错误 43。
通过 Evdev 传递键盘/鼠标
如果您没有多余的鼠标或键盘专用于您的客户机,并且您不想忍受 Spice 的视频开销,您可以设置 evdev 在您的 Linux 主机和虚拟机之间共享它们。
Ctrl 键可在主机和客户机之间切换控制。您可以更改这些热键。您需要为键盘设置 grabToggle 变量为可用的组合之一:ctrl-ctrl、alt-alt、shift-shift、meta-meta、scrolllock 或 ctrl-scrolllock。更多信息:https://github.com/libvirt/libvirt/blob/master/docs/formatdomain.rst#input-devices
首先,在 /dev/input/by-id/ 中找到您的键盘和鼠标设备。只有名称中包含 event 的设备才是有效的。您可能会找到多个与您的鼠标或键盘关联的设备,因此请尝试 cat /dev/input/by-id/device_id,然后按下键盘上的某个键或移动鼠标以查看是否有输入进来,如果有,则说明您找到了正确的设备。现在将这些设备添加到您的配置中
$ virsh edit vmname
...
<devices>
...
<input type='evdev'>
<source dev='/dev/input/by-id/MOUSE_NAME'/>
</input>
<input type='evdev'>
<source dev='/dev/input/by-id/KEYBOARD_NAME' grab='all' repeat='on' grabToggle='ctrl-ctrl'/>
</input>
...
</devices>
将 MOUSE_NAME 和 KEYBOARD_NAME 替换为您的设备路径。现在您可以启动客户机操作系统,并通过同时按下左右控制键来测试主机和客户机之间的鼠标和键盘控制切换。
您还可以考虑在配置中将输入设备从 PS/2 切换到 Virtio。添加这两个设备
$ virsh edit vmname
... <input type='mouse' bus='virtio'/> <input type='keyboard' bus='virtio'/> ...
virtio 输入设备在客户机驱动程序安装之前实际上不会被使用。QEMU 将继续向 PS2 设备发送按键事件,直到它检测到 virtio 输入驱动程序的初始化。请注意,PS2 设备无法移除,因为它们是模拟的 Q35/440FX 芯片组的内部功能。
注意事项
在基于 OVMF 的虚拟机上使用非 EFI 镜像
OVMF 固件不支持从非 EFI 介质引导。如果启动后安装过程将您置于 UEFI shell,您可能拥有一个无效的 EFI 引导介质。尝试使用备用的 Linux/Windows 镜像来确定您是否拥有无效介质。
Wayland 上的键盘事件
Wayland 在会话启动后会阻止对键盘事件的相互访问。要绕过此问题,您必须在 Wayland 会话启动之前启动虚拟机。
性能调优
PCI 直通的大多数用例都与高性能域相关,例如视频游戏和 GPU 加速任务。虽然 PCI 直通本身就是实现原生性能的一步,但仍然需要在主机和客户机上进行一些调整才能充分发挥虚拟机的潜力。
CPU 钉扎
KVM 客户机的默认行为是将来自客户机的操作作为表示虚拟处理器的多个线程运行。这些线程像任何其他线程一样由 Linux 调度程序管理,并根据优先级和优先级队列分派到任何可用的 CPU 核心。因此,当主机调度程序将虚拟 CPU 线程重新调度到不同的物理 CPU 时,局部 CPU 缓存(L1/L2/L3)的优势就会丢失。这会明显损害客户机的性能。CPU 钉扎旨在通过限制虚拟 CPU 允许运行的物理 CPU 来解决此问题。理想的设置是一对一映射,以便虚拟 CPU 核心匹配物理 CPU 核心,同时考虑超线程/SMT。
此外,在某些现代 CPU 中,一组核心通常共享一个公共 L3 缓存。在这种情况下,应注意精确地钉扎共享特定 L3 的物理核心。否则可能导致缓存驱逐,从而导致微卡顿。
CPU 拓扑
大多数现代 CPU 都支持硬件多任务,在 Intel CPU 上称为超线程,在 AMD CPU 上称为 SMT。超线程/SMT 只是一种非常有效的方式,可以在任何给定时间在单个 CPU 核心上运行两个线程。您将需要考虑到您选择的 CPU 钉扎将很大程度上取决于您在虚拟机运行时对主机所做的操作。
要查找 CPU 的拓扑,请运行 lscpu -e
6 核/12 线程 Ryzen 5 1600 上的 lscpu -e
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE MAXMHZ MINMHZ 0 0 0 0 0:0:0:0 yes 3800.0000 1550.0000 1 0 0 0 0:0:0:0 yes 3800.0000 1550.0000 2 0 0 1 1:1:1:0 yes 3800.0000 1550.0000 3 0 0 1 1:1:1:0 yes 3800.0000 1550.0000 4 0 0 2 2:2:2:0 yes 3800.0000 1550.0000 5 0 0 2 2:2:2:0 yes 3800.0000 1550.0000 6 0 0 3 3:3:3:1 yes 3800.0000 1550.0000 7 0 0 3 3:3:3:1 yes 3800.0000 1550.0000 8 0 0 4 4:4:4:1 yes 3800.0000 1550.0000 9 0 0 4 4:4:4:1 yes 3800.0000 1550.0000 10 0 0 5 5:5:5:1 yes 3800.0000 1550.0000 11 0 0 5 5:5:5:1 yes 3800.0000 1550.0000
考虑到 L3 映射,建议钉扎和隔离 CPU 6-11。钉扎和隔离的数量少于这些(例如 8-11)将导致主机系统使用核心 6 和 7 的 L3 缓存,这最终会导致缓存驱逐,从而导致性能不佳。
6 核/12 线程 Intel 8700k 上的 lscpu -e
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE MAXMHZ MINMHZ 0 0 0 0 0:0:0:0 yes 4600.0000 800.0000 1 0 0 1 1:1:1:0 yes 4600.0000 800.0000 2 0 0 2 2:2:2:0 yes 4600.0000 800.0000 3 0 0 3 3:3:3:0 yes 4600.0000 800.0000 4 0 0 4 4:4:4:0 yes 4600.0000 800.0000 5 0 0 5 5:5:5:0 yes 4600.0000 800.0000 6 0 0 0 0:0:0:0 yes 4600.0000 800.0000 7 0 0 1 1:1:1:0 yes 4600.0000 800.0000 8 0 0 2 2:2:2:0 yes 4600.0000 800.0000 9 0 0 3 3:3:3:0 yes 4600.0000 800.0000 10 0 0 4 4:4:4:0 yes 4600.0000 800.0000 11 0 0 5 5:5:5:0 yes 4600.0000 800.0000
由于在此示例中所有核心都连接到同一个 L3,因此只要以正确的线程对进行钉扎和隔离,钉扎和隔离多少个 CPU 并不重要。例如,(0, 6), (1, 7) 等。
正如我们上面看到的,对于 AMD,**Core 0** 与 **CPU 0 和 1** 是顺序的,而 Intel 将 **Core 0** 放在 **CPU 0 和 6** 上。
lstopo 来生成 CPU/线程分组的有用图像。如果您不需要所有核心用于客户机,那么最好至少为主机保留一个核心。选择哪个核心用于主机或客户机应基于您 CPU 的特定硬件特性,但**核心 0** 在大多数情况下是主机的好选择。如果为该主机预留了任何核心,建议将模拟器和 iothreads(如果使用)钉扎到主机核心而不是 VCPU。这可以提高性能并降低客户机的延迟,因为这些线程不会污染缓存或与客户机 VCPU 线程争用调度。如果所有核心都传递给客户机,则无需也无益于钉扎模拟器或 iothreads。
XML 示例
4 核/1 线程 CPU(无超线程)示例
$ virsh edit vmname
...
<vcpu placement='static'>4</vcpu>
<cputune>
<vcpupin vcpu='0' cpuset='0'/>
<vcpupin vcpu='1' cpuset='1'/>
<vcpupin vcpu='2' cpuset='2'/>
<vcpupin vcpu='3' cpuset='3'/>
</cputune>
...
4 核/2 线程 Intel/AMD CPU 示例(ComboPI AGESA BIOS 更新后)
$ virsh edit vmname
...
<vcpu placement='static'>8</vcpu>
<iothreads>1</iothreads>
<cputune>
<vcpupin vcpu='0' cpuset='2'/>
<vcpupin vcpu='1' cpuset='8'/>
<vcpupin vcpu='2' cpuset='3'/>
<vcpupin vcpu='3' cpuset='9'/>
<vcpupin vcpu='4' cpuset='4'/>
<vcpupin vcpu='5' cpuset='10'/>
<vcpupin vcpu='6' cpuset='5'/>
<vcpupin vcpu='7' cpuset='11'/>
<emulatorpin cpuset='0,6'/>
<iothreadpin iothread='1' cpuset='0,6'/>
</cputune>
...
<cpu mode='host-passthrough'>
<topology sockets='1' cores='4' threads='2'/>
</cpu>
...
4 核/2 线程 AMD CPU 示例(ComboPi AGESA BIOS 更新前)
$ virsh edit vmname
...
<vcpu placement='static'>8</vcpu>
<iothreads>1</iothreads>
<cputune>
<vcpupin vcpu='0' cpuset='2'/>
<vcpupin vcpu='1' cpuset='3'/>
<vcpupin vcpu='2' cpuset='4'/>
<vcpupin vcpu='3' cpuset='5'/>
<vcpupin vcpu='4' cpuset='6'/>
<vcpupin vcpu='5' cpuset='7'/>
<vcpupin vcpu='6' cpuset='8'/>
<vcpupin vcpu='7' cpuset='9'/>
<emulatorpin cpuset='0-1'/>
<iothreadpin iothread='1' cpuset='0-1'/>
</cputune>
...
<cpu mode='host-passthrough'>
<topology sockets='1' cores='4' threads='2'/>
</cpu>
...
如果您不打算在虚拟机上同时在主机上进行任何计算密集型工作(甚至任何工作),您可能希望将虚拟机线程钉扎到所有核心上,以便虚拟机能够充分利用主机可用的空闲 CPU 时间。请注意,钉扎 CPU 的所有物理和逻辑核心可能会在客户机虚拟机中引入延迟。
大内存页
在处理需要大量内存的应用程序时,内存延迟可能会成为一个问题,因为使用的内存页越多,该应用程序就越可能尝试访问多个内存“页”(这是内存分配的基本单位)中的信息。解析内存页的实际地址需要多个步骤,因此 CPU 通常会缓存最近使用的内存页的信息,以加快对同一页的后续使用。使用大量内存的应用程序会遇到一个问题,例如,虚拟机使用 4 GiB 内存,分为 4 KiB 页(这是正常页的默认大小),总共 104 万页,这意味着此类缓存未命中会变得极其频繁,并大大增加内存延迟。大页面的存在是为了通过为这些应用程序提供更大的单个页面来缓解此问题,从而增加多次操作连续目标同一页的可能性。
透明大页
QEMU 将自动使用 2MiB 大小的透明大页,无需在 QEMU 或 Libvirt 中进行任何显式配置,但存在一些重要注意事项。使用 VFIO 时,页面在启动时被锁定,并且在虚拟机首次启动时预先分配透明大页。如果内核内存高度碎片化,或者虚拟机使用了剩余可用内存的大部分,那么内核可能没有足够的 2MiB 页来完全满足分配。在这种情况下,它会通过使用 2MiB 和 4KiB 页的混合来默默失败。由于页面在 VFIO 模式下被锁定,内核在虚拟机启动后也无法将这些 4KiB 页转换为大页。可用于 THP 的 2MiB 大页数量与后面章节中描述的#动态大页机制相同。
检查 THP 全局使用了多少内存
$ grep AnonHugePages /proc/meminfo
AnonHugePages: 8091648 kB
检查特定的 QEMU 实例。grep 命令必须替换 QEMU 的 PID。
$ grep -P 'AnonHugePages:\s+(?!0)\d+' /proc/[PID]/smaps
AnonHugePages: 8087552 kB
在此示例中,虚拟机被分配了 8388608KiB 内存,但只有 8087552KiB 可通过 THP 使用。其余 301056KiB 分配为 4KiB 页。除了手动检查外,没有迹象表明发生部分分配。因此,THP 的有效性在很大程度上取决于虚拟机启动时主机系统的内存碎片情况。如果此权衡不可接受或需要严格保证,则建议使用#静态大页。
Arch 内核已编译并默认启用了 THP,/sys/kernel/mm/transparent_hugepage/enabled 设置为 madvise 模式。
静态大页
虽然透明大页在绝大多数情况下都应该有效,但它们也可以在启动时静态分配。这仅在机器支持的情况下才需要使用 1 GiB 大页,因为透明大页通常最多只有 2 MiB。
要在启动时分配大页,只需在内核命令行上指定所需数量,使用 hugepages=x。例如,使用 hugepages=1024 保留 1024 页,每个大页的默认大小为 2048 KiB,则创建 2 GiB 内存供虚拟机使用。
如果 CPU 页大小支持,可以手动设置。可以通过 grep pdpe1gb /proc/cpuinfo 验证 1 GiB 大页支持。通过内核参数设置 1 GiB 大页大小:default_hugepagesz=1G hugepagesz=1G hugepages=X。
此外,由于静态大页只能被明确请求它的应用程序使用,您必须在 libvirt 域配置中添加此部分以允许 kvm 从中受益。
$ virsh edit vmname
... <memoryBacking> <hugepages/> </memoryBacking> ...
动态大页
可以通过 vm.nr_overcommit_hugepages sysctl 参数手动分配 Hugepages。
/etc/sysctl.d/10-kvm.conf
vm.nr_hugepages = 0 vm.nr_overcommit_hugepages = num
其中 num - 是 Hugepages 的数量,默认大小为 2 MiB。页面将被自动分配,并在虚拟机停止后释放。
更手动的方法
# echo num > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages # echo num > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
分别为 2 MiB 和 1 GiB 页大小。它们也应以相同方式手动释放。
强烈建议在启动虚拟机之前,丢弃缓存、压缩内存并等待几秒钟,因为可能没有足够的连续可用内存来满足所需的大页面块。尤其是在主机系统运行一段时间之后。
# echo 3 > /proc/sys/vm/drop_caches # echo 1 > /proc/sys/vm/compact_memory
理论上,1 GiB 页的运行方式与 2 MiB 页相同。但实际上,尚未找到保证获得连续 1 GiB 内存块的方法。每次连续请求 1 GiB 块时,动态分配的数量会越来越少。
CPU 频率调速器
根据您的CPU 调速器的配置方式,虚拟机线程可能无法达到频率提升所需的 CPU 负载阈值。事实上,KVM 本身无法更改 CPU 频率,如果它不像预期的那样随着 vCPU 使用率扩展,可能会导致性能不佳。一个简单的检查方法是查看运行 CPU 密集型任务时 watch lscpu 报告的频率是否会增加。如果您确实遇到卡顿且频率未达到报告的最大值,这可能是由于CPU 扩展由主机操作系统控制。在这种情况下,尝试将所有核心设置为最大频率,看看这是否能提高性能。请注意,如果您使用的是具有默认 pstate 驱动程序的现代 Intel 芯片,cpupower 命令将无效,因此请监控 /proc/cpuinfo 以确保您的 CPU 实际上处于最大频率。
隔离钉扎的 CPU
CPU 钉扎本身并不能阻止其他主机进程在钉扎的 CPU 上运行。正确隔离钉扎的 CPU 可以减少客户机虚拟机的延迟。
使用 isolcpus 内核参数
在此示例中,我们假设您使用的是 CPU 4-7。使用内核参数 isolcpus nohz_full 来完全隔离内核中的 CPU。例如
isolcpus=4-7 nohz_full=4-7
然后,使用 taskset 和 chrt 运行 qemu-system-x86_64
# chrt -r 1 taskset -c 4-7 qemu-system-x86_64 ...
chrt 命令将确保任务调度程序以轮循方式分发工作(否则所有工作都将停留在第一个 CPU 上)。对于 taskset,CPU 号可以是以逗号和/或破折号分隔的,例如“0,1,2,3”或“0-4”或“1,7-8,10”等。
有关更多信息,请参阅Removeddit 镜像的 Internet Archive 副本,这是一个 Reddit 线程。
动态隔离 CPU
isolcpus 内核参数将永久保留 CPU 核心,即使在客户机未运行时也是如此。一个更灵活的替代方案是在启动客户机时动态隔离 CPU。这可以通过以下替代方案来实现
- cpuset-gitAUR(vfio-users 帖子,博客文章,示例脚本)
- vfio-isolateAUR
- systemd
使用 systemd 的示例
在此示例中,我们假设有一个拥有 12 个 CPU 的主机,其中 CPU 2-5 和 8-11 被钉扎到客户机。然后运行以下命令将主机隔离到 CPU 0、1、6 和 7。
# systemctl set-property --runtime -- user.slice AllowedCPUs=0,1,6,7 # systemctl set-property --runtime -- system.slice AllowedCPUs=0,1,6,7 # systemctl set-property --runtime -- init.scope AllowedCPUs=0,1,6,7
关闭客户机后,运行以下命令将所有 12 个 CPU 重新分配给主机。
# systemctl set-property --runtime -- user.slice AllowedCPUs=0-11 # systemctl set-property --runtime -- system.slice AllowedCPUs=0-11 # systemctl set-property --runtime -- init.scope AllowedCPUs=0-11
您可以使用libvirt 钩子在客户机的启动/关闭时自动运行上述命令,如下所示。
创建或编辑 /etc/libvirt/hooks/qemu 并添加以下内容。
/etc/libvirt/hooks/qemu
#!/bin/sh
command=$2
if [ "$command" = "started" ]; then
systemctl set-property --runtime -- system.slice AllowedCPUs=0,1,6,7
systemctl set-property --runtime -- user.slice AllowedCPUs=0,1,6,7
systemctl set-property --runtime -- init.scope AllowedCPUs=0,1,6,7
elif [ "$command" = "release" ]; then
systemctl set-property --runtime -- system.slice AllowedCPUs=0-11
systemctl set-property --runtime -- user.slice AllowedCPUs=0-11
systemctl set-property --runtime -- init.scope AllowedCPUs=0-11
fi
然后使其可执行。
重新启动 libvirtd.service,然后启动您的虚拟机。如果您现在在主机上创建了非常多线程的负载,您应该会看到它将您选择的 CPU 保持在负载之外,而虚拟机仍然可以利用它们。您还应该看到,一旦您终止了虚拟机,这些 CPU 就会自动完全被您的主机使用。
请注意,这需要 systemd 244 或更高版本,以及cgroups v2,后者现在默认启用。
提高 AMD CPU 的性能
从 QEMU 3.1 开始,TOPOEXT cpuid 标志默认禁用。为了在 AMD CPU 上使用超线程 (SMT),您需要手动启用它。
<cpu mode='host-passthrough' check='none'> <topology sockets='1' cores='4' threads='2'/> <feature policy='require' name='topoext'/> </cpu>
提交:https://gitlab.com/qemu-project/qemu/-/commit/7210a02c58572b2686a3a8d610c6628f87864aed
Virtio 磁盘
开箱即用的默认磁盘类型是 SATA 或 IDE 仿真。这些控制器提供最大的兼容性,但不适合高效虚拟化。有两种加速模型:virtio-scsi 用于 SCSI 仿真和直通,或 virtio-blk 用于更基本的块设备仿真。
驱动程序
- Linux 客户机应该会在任何现代内核上开箱即用支持它们。
- macOS 自 Mojave 版本起通过
AppleVirtIO.kext支持virtio-blk。 - Windows 需要Windows virtio 驱动程序。
virtio-scsi使用vioscsi驱动程序。virtio-blk使用viostor驱动程序。 - Windows 可以直接安装在这些磁盘上,方法是在安装程序的磁盘选择菜单中选择“加载驱动程序”。安装过程中应将 Windows ISO 和 virtio 驱动程序 ISO 都连接为常规 SATA/IDE 光驱。
- 将现有 Windows 安装的引导磁盘切换到 virtio
virtio-blk:添加一个总线类型为virtio的临时磁盘,启动 Windows 并加载磁盘驱动程序,然后关机并将引导磁盘的总线类型切换为virtio。virtio-scsi:添加一个模型为virtio的 SCSI 控制器,启动 Windows 并加载控制器的驱动程序,然后关机并将引导磁盘的总线类型切换为scsi(而不是 virtio)。
考虑因素
virtio-scsi的 TRIM 支持已经成熟,所有版本都应该支持它。传统上,virtio-scsi是出于这个原因而首选的方法。virtio-blk的 TRIM 支持是新的,这需要 qemu 4.0+,客户机 Linux 内核 5.0+,客户机 Windows 驱动程序 0.1.173+。- 精简配置通过在稀疏镜像文件上启用 TRIM 来工作:
discard='unmap'。未使用的块将被释放,磁盘使用量将下降(在 raw 和 qcow2 上均有效)。稀疏镜像文件的实际磁盘大小可以使用du /path/to/disk.img检查。 - 精简配置也可以与块存储一起使用,例如 zfs zvols 或 thin lvm。
- Virt 队列计数将影响客户机内核中用于 IO 处理的线程数量,建议使用
queues='4'或更多。 - 原生模式 (
io='native') 使用基于 Linux AIO 的单线程模型,CPU 效率稍高,但峰值性能可能较低,并且不允许使用主机端缓存。 - 线程模式 (
io='threads') 将在磁盘使用时按需生成数十个线程。这效率较低,但如果主机核心足够多,可能会表现更好,并且允许使用主机端缓存。 - libvirt 的现代版本会将线程模式下动态创建的工作线程与 iothread=1 cgroup 分组以便进行钉扎。非常旧的 libvirt 版本将这些线程留在模拟器 cgroup 中。
IO 线程
IO 线程是用于处理磁盘事件的专用线程,而不是使用主 qemu 模拟器循环。这不应与 io='threads' 按需生成的线程混淆。
- 每个磁盘控制器只能使用一个 iothread。线程必须通过
<driver>标签中的iothread='X'分配给特定的控制器。此外,额外的和未分配的 iothread 将不会被使用,也无济于事。 - 对于
virtio-scsi,有一个控制器用于多个 SCSI 磁盘。iothread 分配在控制器上:<controller><driver iothread='X'>。 - 对于
virtio-blk,每个磁盘都有自己的控制器。iothread 分配在磁盘本身的驱动程序标签下:<disk><driver iothread='X'>。 - 由于模拟磁盘会产生大量的 CPU 开销,这可能导致在高磁盘负载(尤其是高随机 IOPS)下 vcpu 卡顿。在这种情况下,将 IO 钉扎到不同于 vcpu 的核心有助于使用
<iothreadpin>。
libvirt 示例
virtio-scsi + iothread + 工作线程 + 主机端写回缓存 + 完整磁盘块设备后端
<domain>
<devices>
<disk type='block' device='disk'>
<driver name='qemu' type='raw' cache='writeback' io='threads' discard='unmap'/>
<source dev='/dev/disk/by-id/ata-Samsung_SSD_840_EVO_1TB_S1D9NSAF206396F'/>
<target dev='sda' bus='scsi'/>
</disk>
<controller type='scsi' index='0' model='virtio-scsi'>
<driver iothread='1' queues='8'/>
</controller>
virtio-blk + iothread + 原生 aio + 无主机缓存 + 原始稀疏镜像后端
<domain>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='raw' cache='none' io='native' discard='unmap' iothread='1' queues='8'/>
<source file='/var/lib/libvirt/images/pool/win10.img'/>
<target dev='vda' bus='virtio'/>
</disk>
创建 iothreads
<domain> <iothreads>1</iothreads>
钉扎 iothreads
<domain>
<cputune>
<iothreadpin iothread='1' cpuset='0-1,6-7'/>
virt-manager 示例
这将创建一个 virtio-blk 设备。
- 打开虚拟机首选项
- 转到
Add Hardware > Storage - 创建或选择一个存储文件
- 选择
Device Type: Disk device和Bus type: VirtIO - 单击 Finish
Virtio 网络
默认的 NIC 模型 rtl8139 或 e1000 可能会成为千兆或更高速度的瓶颈,并且与 virtio-net 相比,CPU 开销很大。
- 使用 libvirt 为 NIC 选择
virtio作为模型,或在裸 qemu 中使用virtio-net-pci设备。 - Windows 需要Windows virtio 驱动程序中的
NetKVM驱动程序。 - Virtio 默认使用 vhost-net 进行内核内数据包处理,而无需退出到用户空间。
- 可以通过添加
<driver queues='8'/>在接口标签下启用多队列以进一步加速多连接,但这通常不会提高单流速度。 - 零拷贝传输也可以在 macvtap 上启用,方法是设置模块参数
vhost_net.experimental_zcopytx=1,但这实际上可能会导致性能变差,请参见提交。
带桥接的 Libvirt 示例
<interface type='bridge'> <mac address="52:54:00:6d:6e:2e"/> <source bridge='br0'/> <model type='virtio'/> <driver queues='8'/> </interface>
带桥接的 MACVTAP 示例
<interface type="direct"> <source dev="eno1" mode="vepa"/> <target dev="macvtap0"/> <model type="virtio"/> <alias name="net0"/> </interface>
模式的可能选项是 'vepa'、'bridge'、'private' 和 'passthrough'。一个包含差异描述的指南来自 redhat:[4]。
用您自己的设备地址替换源 /dev 设备。您可以使用以下命令获取本地地址。
$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 30:9c:23:ac:51:d0 brd ff:ff:ff:ff:ff:ff altname enp0s31f6
进一步优化
更多专业的虚拟机优化技巧可在Red Hat 的虚拟化调优和优化指南中找到。
特殊程序
某些设置需要特定的配置调整才能正常工作。如果您在让主机或虚拟机正常工作时遇到问题,请查看您的系统是否与以下任一情况匹配,并尝试相应地调整您的配置。
使用相同的客户机和主机 GPU
由于 vfio-pci 在启动时使用您的供应商和设备 ID 对来识别需要绑定的设备,如果您有两个共享此类 ID 对的 GPU,您将无法仅通过其中一个 GPU 绑定您的 passthrough 驱动程序。这种设置需要使用脚本,以便您正在使用的任何驱动程序都能通过 `driver_override` 机制根据 PCI 总线地址进行分配。
脚本变体
Passthrough 除启动 GPU 之外的所有 GPU
在这里,我们将创建一个脚本来将 vfio-pci 绑定到除启动 GPU 之外的所有 GPU。创建脚本 ` /usr/local/bin/vfio-pci-override.sh`
#!/bin/sh
for i in /sys/bus/pci/devices/*/boot_vga; do
if [ $(cat "$i") -eq 0 ]; then
GPU="${i%/boot_vga}"
AUDIO="$(echo "$GPU" | sed -e "s/0$/1/")"
USB="$(echo "$GPU" | sed -e "s/0$/2/")"
echo "vfio-pci" > "$GPU/driver_override"
if [ -d "$AUDIO" ]; then
echo "vfio-pci" > "$AUDIO/driver_override"
fi
if [ -d "$USB" ]; then
echo "vfio-pci" > "$USB/driver_override"
fi
fi
done
modprobe -i vfio-pci
Passthrough 指定的 GPU
在这种情况下,我们将手动指定要绑定的 GPU。
#!/bin/sh
DEVS="0000:03:00.0 0000:03:00.1"
if [ ! -z "$(ls -A /sys/class/iommu)" ]; then
for DEV in $DEVS; do
echo "vfio-pci" > /sys/bus/pci/devices/$DEV/driver_override
done
fi
modprobe -i vfio-pci
基于 GPU 的 Passthrough IOMMU 组
简化从指定 GPU Passthrough 其他必要设备。例如显卡的板载音频、USB 和 RGB 控制器。
#!/bin/sh
DEVS="0000:03:00.0"
if [ ! -z "$(ls -A /sys/class/iommu)" ]; then
for DEV in $DEVS; do
for IOMMUDEV in $(ls /sys/bus/pci/devices/$DEV/iommu_group/devices) ; do
echo "vfio-pci" > /sys/bus/pci/devices/$IOMMUDEV/driver_override
done
done
fi
modprobe -i vfio-pci
脚本安装
编辑 /etc/mkinitcpio.conf
编辑 ` /etc/modprobe.d/vfio.conf`
- 添加以下行: ` install vfio-pci /usr/local/bin/vfio-pci-override.sh`
- 重新生成 initramfs 并重启。
将启动 GPU 传递给客户机
标记为 `boot_vga` 的 GPU 在进行 PCI passthrough 时是一个特殊情况,因为 BIOS 需要使用它来显示启动消息或 BIOS 配置菜单等内容。为此,它会创建 VGA 启动 ROM 的副本,该副本可以被自由修改。此修改后的副本是系统看到的版本,passthrough 驱动程序可能会因无效而拒绝。因此,通常建议在 BIOS 配置中更改启动 GPU,以便使用主机 GPU,或者,如果不可能,则在机器本身中交换主机和客户机卡。
使用 Looking Glass 将客户机屏幕流式传输到主机
可以使用 Looking Glass 使虚拟机共享显示器,并可选择共享键盘和鼠标。
向虚拟机添加 IVSHMEM 设备
Looking glass 通过在主机和客户机之间创建共享内存缓冲区工作。这比通过 localhost 流式传输帧快得多,但需要额外的设置。
关闭虚拟机后,打开机器配置
$ virsh edit vmname
...
<devices>
...
<shmem name='looking-glass'>
<model type='ivshmem-plain'/>
<size unit='M'>32</size>
</shmem>
</devices>
...
您应该用您自己根据将要 passthrough 的分辨率计算出的值替换 32。可以这样计算:
width x height x 4 x 2 = total bytes total bytes / 1024 / 1024 = total mebibytes + 10
例如,对于 1920x1080
1920 x 1080 x 4 x 2 = 16,588,800 bytes 16,588,800 / 1024 / 1024 = 15.82 MiB + 10 = 25.82
结果必须**向上舍入**到最接近的 2 的幂,由于 25.82 大于 16,我们应该选择 32。
接下来创建一个配置文件以在启动时创建共享内存文件
/etc/tmpfiles.d/10-looking-glass.conf
f /dev/shm/looking-glass 0660 user kvm -
将 user 替换为您的用户名。
请求 systemd-tmpfiles 现在创建共享内存文件,而无需等待下次启动
# systemd-tmpfiles --create /etc/tmpfiles.d/10-looking-glass.conf
将 IVSHMEM 主机安装到 Windows 客户机
目前 Windows 不会通知用户新 IVSHMEM 设备,它会静默安装一个dummy驱动程序。要实际启用该设备,您必须进入设备管理器,在 **"System Devices"** 下更新 **"PCI standard RAM Controller"** 的驱动程序。从 Red Hat 下载签名的驱动程序 。
安装驱动程序后,您必须下载与您将从 AUR 安装的客户端匹配的 looking-glass-host 软件包,并将其安装在您的客户机上。为了运行它,您还需要从 Microsoft 安装 Microsoft Visual C++ Redistributable。最新版本将自动安装一个服务,该服务会在启动时启动守护程序。主机守护程序的日志位于客户机系统上的 ` %ProgramData%\Looking Glass (host)\looking-glass-host.txt`。
设置 null 视频设备
(来源: https://looking-glass.io/docs/stable/install/#spice-server)
如果您想使用 Spice 来提供键盘和鼠标输入以及剪贴板同步支持,请确保您有一个 ` <graphics type='spice'>` 设备,然后
- 找到您的 ` <video>` 设备,并将 ` <model type='none'/>` 设置为 `none`
- 如果找不到,请确保您有一个 ` <graphics>` 设备,保存并再次编辑
获取客户端
Looking glass 客户端可以从 AUR 使用 looking-glassAUR 或 looking-glass-gitAUR 软件包安装。
虚拟机设置并运行后,您可以启动它
$ looking-glass-client
如果您不想使用 Spice 控制客户机的鼠标和键盘,可以禁用 Spice 服务器。
$ looking-glass-client -s
此外,您可能希望全屏启动 Looking Glass 客户端,否则图像可能会被缩小,导致图像保真度下降。
$ looking-glass-client -F
要阻止主机屏幕保护程序,请使用 `-S` 标志启动 Looking Glass。
$ looking-glass-client -S
这在某些桌面环境中不起作用,包括 KDE。KDE 的问题可能与此行为有关。要在使用 Looking Glass 和 KDE 时暂时禁用主机的屏幕保护程序,可以将 Looking Glass 包装在以下脚本中:
#!/bin/sh kwriteconfig6 --file kscreenlockerrc --group Daemon --key Autolock false qdbus org.freedesktop.ScreenSaver /ScreenSaver configure looking-glass-client kwriteconfig6 --file kscreenlockerrc --group Daemon --key Autolock true qdbus org.freedesktop.ScreenSaver /ScreenSaver configure
使用 `--help` 选项启动以获取更多信息。
附加信息
有关更多详细信息,请参阅上游文档。
在主机和客户机之间切换外围设备
Looking Glass 包含一个 Spice 客户端,用于控制 Windows 客户机上的鼠标移动。然而,对于某些应用程序(如游戏)来说,这可能会有太高的延迟。另一种方法是 passthrough 特定 USB 设备以获得最低延迟。这允许在主机和客户机之间切换设备。
首先创建一个 .xml 文件,用于您希望 passthrough 的设备,libvirt 将使用该文件来识别设备。
~/.VFIOinput/input_1.xml
<hostdev mode='subsystem' type='usb'> <source> <vendor id='0x[Before Colon]'/> <product id='0x[After Colon]'/> </source> </hostdev>
用 `lsusb` 命令的内容(特定于您要 passthrough 的设备)替换 [冒号之前/之后]。
例如,我的鼠标是 `Bus 005 Device 002: ID 1532:0037 Razer USA, Ltd`,所以我会用 1532 替换 `vendor id`,用 0037 替换 `product id`。
重复此过程以 passthrough 您想要的其他 USB 设备。如果您的鼠标/键盘在 `lsusb` 中有多个条目,也许是因为它是无线的,那么为每个条目创建额外的 xml 文件。
接下来需要一个 bash 脚本文件来告诉 libvirt 要将 USB 设备附加/分离到客户机。
~/.VFIOinput/input_attach.sh
#!/bin/sh virsh attach-device [VirtualMachine-Name] [USBdevice]
将 `[VirtualMachine-Name]` 替换为您的虚拟机的名称,可以在 virt-manager 中看到。此外,将 `[USBdevice]` 替换为您希望 passthrough 的设备的 .xml 文件的**完整**路径。添加其他行以 passthrough 多个设备。例如,这是我的脚本:
~/.VFIOinput/input_attach.sh
#!/bin/sh virsh attach-device win10 /home/$USER/.VFIOinput/input_mouse.xml virsh attach-device win10 /home/$USER/.VFIOinput/input_keyboard.xml
接下来复制脚本文件,并将 `attach-device` 替换为 `detach-device`。确保两个脚本都可执行。
这两个脚本文件现在可以执行以将 USB 设备从主机附加或分离到客户机虚拟机。重要的是要注意,它们可能需要以 root 身份执行。要从 Windows 虚拟机运行脚本,一种可能性是使用 PuTTY 通过 SSH 连接到主机,然后执行脚本。在 Windows 上,PuTTY 附带 plink.exe,它可以执行单个命令通过 SSH,然后在后台注销,而不是打开 SSH 终端。
detach_devices.bat
"C:\Program Files\PuTTY\plink.exe" root@$HOST_IP -pw $ROOTPASSWORD /home/$USER/.VFIOinput/input_detach.sh
将 `$HOST_IP` 替换为主机的 IP 地址,将 `$ROOTPASSWORD` 替换为 root 密码。
您可能还想通过按键绑定执行脚本文件。在 Windows 上,一个选项是 Autohotkey,在主机上是 Xbindkeys。由于需要以 root 身份运行脚本,您可能还需要使用 Polkit 或 Sudo,它们都可以用于对特定可执行文件进行身份验证,使其能够以 root 身份运行而无需密码。
绕过 IOMMU 组(ACS 覆盖补丁)
如果发现您的 PCI 设备与其他您不想 passthrough 的设备分组在一起,您可以使用 Alex Williamson 的 ACS 覆盖补丁来分离它们。请确保您了解这样做的潜在风险。
您需要一个应用了补丁的内核。获取此补丁的最简单方法是通过 linux-zen 或 linux-vfioAUR 软件包。
此外,ACS 覆盖补丁需要通过内核命令行选项启用。补丁文件添加了以下文档:
pcie_acs_override =
[PCIE] Override missing PCIe ACS support for:
downstream
All downstream ports - full ACS capabilties
multifunction
All multifunction devices - multifunction ACS subset
id:nnnn:nnnn
Specfic device - full ACS capabilities
Specified as vid:did (vendor/device ID) in hex
选项 `pcie_acs_override=downstream,multifunction` 应该能分离尽可能多的设备。
安装和配置后,重新配置您的 内核参数以加载带有 `pcie_acs_override=` 选项启用的新内核。
纯 QEMU,无 libvirt
而不是通过 libvirt 设置虚拟机,可以使用自定义参数的纯 QEMU 命令来运行打算用于 PCI passthrough 的虚拟机。对于脚本化设置等用例,这可能是可取的,因为需要与其他脚本配合使用的灵活性。
要实现这一点,在 #Setting up IOMMU 和 #Isolating the GPU 之后,按照 QEMU 文章设置虚拟化环境,启用 KVM,然后使用 ` -device vfio-pci,host=07:00.0` 标志,将标识符(07:00.0)替换为您之前用于 GPU 隔离的实际设备 ID。
要利用 OVMF 固件,请确保安装了 `edk2-ovmf` 软件包,将 UEFI 变量从 `/usr/share/edk2/x64/OVMF_VARS.4m.fd` 复制到临时位置,如 `/tmp/my_OVMF_VARS.4m.fd`,最后通过将以下参数附加到 QEMU 命令来指定 OVMF 路径(顺序很重要):
- `-drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd` 用于实际的 OVMF 固件二进制文件,注意 readonly 选项
- `-drive if=pflash,format=raw,file=/tmp/my_OVMF_VARS.4m.fd` 用于变量
- 确保 `OVMF_CODE.4m.fd` 在 `my_OVMF_VARS.4m.fd` 之前作为命令行参数给出。否则启动顺序将失败。
- QEMU 的默认 SeaBIOS 可以代替 OVMF 使用,但不推荐,因为它可能导致 passthrough 设置问题。
建议研究 QEMU 文章,了解如何使用 virtio 驱动程序和其他进一步配置来提高性能。
您可能还需要使用 `-cpu host,kvm=off` 参数将主机的 CPU 模型信息转发给虚拟机,并欺骗 Nvidia 和可能其他制造商的设备驱动程序用于阻止在虚拟化系统中完全使用硬件的虚拟化检测。
Passthrough 其他设备
USB 控制器
如果您的主板有多个 USB 控制器映射到多个组,可以 passthrough 这些控制器而不是 USB 设备。通过 passthrough 实际控制器而不是单个 USB 设备具有以下优点:
- 如果设备在给定操作过程中断开连接或更改 ID(例如,手机正在更新),虚拟机不会突然停止看到它。
- 此控制器管理的任何 USB 端口都由虚拟机直接处理,其设备可以拔出、插回和更换,而无需通知 hypervisor。
- 当您通常 passthrough 的 USB 设备在启动虚拟机时缺失时,Libvirt 不会抱怨。
与 GPU 不同,大多数 USB 控制器的驱动程序不需要任何特定配置即可在虚拟机中运行,并且主机和客户机系统之间的控制可以正常来回传递,而不会产生任何副作用。
您可以使用此命令找出哪些 USB 设备对应哪个控制器,以及各种端口和设备是如何分配给它们的。
$ for usb_ctrl in /sys/bus/pci/devices/*/usb*; do pci_path=${usb_ctrl%/*}; iommu_group=$(readlink $pci_path/iommu_group); echo "Bus $(cat $usb_ctrl/busnum) --> ${pci_path##*/} (IOMMU group ${iommu_group##*/})"; lsusb -s ${usb_ctrl#*/usb}:; echo; done
Bus 1 --> 0000:00:1a.0 (IOMMU group 4) Bus 001 Device 004: ID 04f2:b217 Chicony Electronics Co., Ltd Lenovo Integrated Camera (0.3MP) Bus 001 Device 007: ID 0a5c:21e6 Broadcom Corp. BCM20702 Bluetooth 4.0 [ThinkPad] Bus 001 Device 008: ID 0781:5530 SanDisk Corp. Cruzer Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 2 --> 0000:00:1d.0 (IOMMU group 9) Bus 002 Device 006: ID 0451:e012 Texas Instruments, Inc. TI-Nspire Calculator Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
这台笔记本电脑有 3 个 USB 端口由 2 个 USB 控制器管理,每个控制器都有自己的 IOMMU 组。在此示例中,总线 001 管理单个 USB 端口(其中插入了 SanDisk USB 闪存驱动器,因此它出现在列表中),但也管理许多内部设备,如内置摄像头和蓝牙卡。另一方面,总线 002 似乎除了插入的计算器之外,不管理任何东西。第三个端口是空的,因此它不会出现在列表中,但实际上由总线 002 管理。
一旦您通过插入各种设备识别出哪个控制器管理哪些端口,并决定要 passthrough 哪个控制器,只需将其添加到虚拟机在客户机配置中控制的 PCI 主机设备列表中即可。不需要其他配置。
通过 PipeWire 直接将虚拟机音频传递到主机
这比将音频传递到 pipewire 的 pulseaudio 仿真层更可取。
$ virsh edit vmname
<devices>
...
<audio id="1" type="pipewire" runtimeDir="/run/user/1000">
<input name="qemuinput"/>
<output name="qemuoutput"/>
</audio>
</devices>
(请将 runtimeDir 值从 1000 相应地更改为您的桌面用户 uid)
为了解决“Failed to initialize PW context”错误,您可以修改 qemu 配置以使用您的用户。
/etc/libvirt/qemu.conf
user = "example"
通过 PulseAudio 将虚拟机音频传递到主机
可以使用 libvirt 将虚拟机的音频路由到主机作为应用程序。这样做的优点是可以将多个音频流路由到一个主机输出,并与不支持 passthrough 的音频输出设备配合使用。这需要 PulseAudio 在主机系统上运行。
首先,安装 `qemu-audio-pa`。
然后,删除 `#user = ""` 行的注释。然后在引号中添加您的用户名。这会告诉 QEMU 通过哪个用户来路由 pulseaudio 流。
/etc/libvirt/qemu.conf
user = "example"
模拟的音频设置由两个组件组成:一个暴露给客户机的模拟声音设备和一个将声音设备连接到主机 PulseAudio 的音频后端。
在可用的模拟声音设备中,有两个特别值得关注:ICH9 和 usb-audio。ICH9 同时支持输入和输出,但仅限于立体声。usb-audio 仅支持音频输出,但支持高达 6 声道 5.1 配置。对于 ICH9,请删除 `< etkinlik>` 部分中任何现有的音频后端,然后添加:
$ virsh edit vmname
<sound model='ich9'>
<codec type='micro'/>
<audio id='1'/>
</sound>
<audio id='1' type='pulseaudio' serverName='/run/user/1000/pulse/native'/>
注意匹配的 `id` 元素。上面的示例假定是一个单用户系统,用户 ID 为 1000。使用 `id` 命令查找正确的 ID。您也可以在有多个用户访问 PulseAudio 时使用 `/tmp` 目录。
$ virsh edit vmname
<audio id='1' type='pulseaudio' serverName='unix:/tmp/pulse-socket'/>
如果出现噼啪声或失真的声音,请尝试调整一些延迟设置。以下示例使用 20000 微秒:
$ virsh edit vmname
<audio id="1" type="pulseaudio" serverName="/run/user/1000/pulse/native">
<input latency="20000"/>
<output latency="20000"/>
</audio>
您也可以尝试禁用 QEMU 中包含的软件混音器。理论上,这应该更有效率,并允许更低的延迟,因为混音将仅在您的主机上进行。
$ virsh edit vmname
<audio id="1" type="pulseaudio" serverName="/run/user/1000/pulse/native">
<input mixingEngine="no"/>
<output mixingEngine="no"/>
</audio>
对于 usb-audio,相应的元素读取:
$ virsh edit vmname
<sound model='usb'>
<audio id='1'/>
</sound>
<audio id='1' type='pulseaudio' serverName='/run/user/1000/pulse/native'/>
但是,如果需要 5.1 配置,则需要通过 QEMU 命令行参数配置声音设备:
$ virsh edit vmname
</devices>
<qemu:commandline>
<qemu:arg value='-device'/>
<qemu:arg value='usb-audio,id=sound0,audiodev=audio1,multi=on'/>
</qemu:commandline>
</domain>
`audiodev` 标签必须与音频后端的 `id` 元素匹配。`id='1'` 对应 `audio1`,依此类推。
- 您可以拥有多个音频后端,只需在 XML 中指定多个 `
- Libvirt/QEMU 需要 USB 3 仿真来启用 usb-audio。
- 建议使用工具(如 [6])在 ICH9 音频设备上启用 MSI 中断,以减轻任何噼啪声、卡顿、加速或虚拟机重启后无音频的问题。
- 如果音频仍然出现噼啪/卡顿/加速等问题,您可能需要调整 `buffer-length` 和 `timer-period` 等参数,有关这些参数的更多信息可以在 qemu(1) 手册中找到。
- 某些音频芯片组,如 Realtek alc1220,也可能存在开箱即用的问题,因此在使用 QEMU 的任何音频仿真时都要考虑这一点。
- 不正确的固定 CPU 或在未使用 isolcpus 的情况下大量使用主机,也会影响声音错误,特别是在虚拟机游戏中。
通过 JACK 和 PipeWire 将虚拟机音频传递到主机
也可以通过 JACK 和 PipeWire 将虚拟机的音频传递到主机。
首先,请确保您有一个包含 JACK 支持的正常运行的 PipeWire 设置。
接下来,您需要告诉 libvirt 以您的用户身份运行 QEMU:
/etc/libvirt/qemu.conf
user = "example"
不要忘记 重启 `libvirtd.service`。
最后一步,需要扩展 XML 架构以允许传递环境变量。为此,请修改虚拟机域配置:
$ virsh edit vmname
<domain type='kvm'>
更改为
$ virsh edit vmname
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
然后,您可以将实际的音频配置添加到您的虚拟机:
$ virsh edit vmname
<devices>
...
<audio id="1" type="jack">
<input clientName="vm-win10" connectPorts="your-input"/>
<output clientName="vm-win10" connectPorts="your-output"/>
</audio>
</devices>
<qemu:commandline>
<qemu:env name="PIPEWIRE_RUNTIME_DIR" value="/run/user/1000"/>
<qemu:env name="PIPEWIRE_LATENCY" value="512/48000"/>
</qemu:commandline>
</domain>
注意匹配的 `id` 元素。上面的示例假定一个单用户系统,用户 ID 为 1000。使用 `id` 命令查找正确的 ID。
您可能需要调整 `PIPEWIRE_LATENCY` 值以获得所需的低延迟,而不会出现噼啪声。
通过 Scream 将虚拟机音频传递到主机
可以通过桥接网络(如 Libvirt 提供的)或通过添加 IVSHMEM 设备到主机来传递虚拟机的音频,使用一个名为 Scream 的应用程序。本节仅涵盖在主机上使用 PulseAudio 作为接收器。有关更多详细信息和不同方法的说明,请参阅项目页面。
使用 Scream 和桥接网络
- 这是首选的使用方式,尽管结果可能因用户而异。
- 建议在使用 Scream 时使用 #Virtio network 网卡,QEMU 提供的其他虚拟网卡(如 **e1000e**)可能导致性能下降。
要通过网络使用 scream,您需要通过 `ip a` 找到您的桥接名称,在大多数情况下它将被命名为 **br0** 或 **virbr0**。下面是启动 Scream 应用程序所需命令的示例:
$ scream -o pulse -i virbr0 &
添加 IVSHMEM 设备以将 Scream 与 IVSHMEM 一起使用
关闭虚拟机后,编辑机器配置
$ virsh edit vmname
...
<devices>
...
<shmem name='scream-ivshmem'>
<model type='ivshmem-plain'/>
<size unit='M'>2</size>
</shmem>
</devices>
...
在上面的配置中,IVSHMEM 设备的大小为 2MB(推荐数量)。根据需要更改此值。
现在请参阅 #Adding IVSHMEM Device to virtual machines 以在启动时配置主机创建共享内存文件,将 `looking-glass` 替换为 `scream-ivshmem`。
配置 Windows 客户机以使用 IVSHMEM
必须在客户机上安装正确的 IVSHMEM 设备驱动程序。请参阅 #Installing the IVSHMEM Host to Windows guest。忽略关于 `looking-glass-host` 的部分。
在客户机上安装 Scream 虚拟音频驱动程序。如果您为虚拟机启用了安全启动,则可能需要禁用它。
使用注册表编辑器,将 DWORD `HKLM\SYSTEM\CurrentControlSet\Services\Scream\Options\UseIVSHMEM` 设置为 IVSHMEM 设备的大小(以 MB 为单位)。请注意,scream 使用其大小来标识其 IVSHMEM 设备,因此请确保只有一个大小相同的设备(建议的默认值为 2MB 的 `2`)。
使用管理员 CMD shell 中的以下命令创建密钥和 DWORD: `REG ADD HKLM\SYSTEM\CurrentControlSet\Services\Scream\Options /v UseIVSHMEM /t REG_DWORD /d 2` (来源:scream on Github)
配置主机
安装 screamAUR。
创建一个 systemd 用户服务来控制接收器
~/.config/systemd/user/scream-ivshmem-pulse.service
[Unit] Description=Scream IVSHMEM pulse receiver After=pulseaudio.service Wants=pulseaudio.service [Service] Type=simple ExecStartPre=/usr/bin/truncate -s 0 /dev/shm/scream-ivshmem ExecStartPre=/usr/bin/dd if=/dev/zero of=/dev/shm/scream-ivshmem bs=1M count=2 ExecStart=/usr/bin/scream -m /dev/shm/scream-ivshmem [Install] WantedBy=default.target
将 `count=2` 编辑为 IVSHMEM 设备的大小(以 MiB 为单位)。
现在启动 `scream-ivshmem-pulse.service` 用户单元。
物理磁盘/分区
Raw 和 qcow2 尤其会为繁重的 IO 带来显著的开销。可以直接使用整个磁盘或分区来绕过文件系统并提高 I/O 性能。如果您希望原生双启动客户机操作系统,则需要 passthrough 整个磁盘而无需分区。建议使用 /dev/disk/by-paths 来引用磁盘,因为 /dev/sdX 条目在每次启动时都可能不同。要找出哪个磁盘/分区与您想要 passthrough 的磁盘/分区相关:
$ ls -l /dev/disk/by-id/*
/dev/disk/by-id/ata-ST1000LM002-9VQ14L_Z0501SZ9 -> ../../sdd
请参阅 #Virtio disk,了解如何使用 libvirt XML 添加这些。您也可以通过 Virt-Manager 的 **Add Hardware** 菜单添加磁盘,然后在 **Select or create custom storage** 框中输入您想要的磁盘,例如:**/dev/disk/by-id/ata-ST1000LM002-9VQ14L_Z0501SZ9**
注意事项
Passthrough 不支持重置的设备
当虚拟机关闭时,客户机使用的所有设备都会由其操作系统进行去初始化,以准备关闭。在这种状态下,这些设备不再工作,必须先进行断电循环才能恢复正常运行。Linux 可以自行处理这种断电循环,但当设备没有已知的重置方法时,它仍处于禁用状态,并且不可用。由于 Libvirt 和 Qemu 都期望在完全停止虚拟机之前,所有主机 PCI 设备都已准备好重新连接到主机,因此当遇到无法重置的设备时,它们将挂起在“正在关闭”状态,直到主机系统重启后才能重新启动。因此,建议仅 passthrough 内核能够重置的 PCI 设备,这可以通过 PCI 设备 sysfs 节点中 `reset` 文件的存在来证明,例如 `/sys/bus/pci/devices/0000:00:1a.0/reset`。
以下 bash 命令显示了哪些设备可以重置,哪些不能。
for iommu_group in $(find /sys/kernel/iommu_groups/ -maxdepth 1 -mindepth 1 -type d);do echo "IOMMU group $(basename "$iommu_group")"; for device in $(\ls -1 "$iommu_group"/devices/); do if [[ -e "$iommu_group"/devices/"$device"/reset ]]; then echo -n "[RESET]"; fi; echo -n $'\t';lspci -nns "$device"; done; done
IOMMU group 0 00:00.0 Host bridge [0600]: Intel Corporation Xeon E3-1200 v2/Ivy Bridge DRAM Controller [8086:0158] (rev 09) IOMMU group 1 00:01.0 PCI bridge [0604]: Intel Corporation Xeon E3-1200 v2/3rd Gen Core processor PCI Express Root Port [8086:0151] (rev 09) 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GK208 [GeForce GT 720] [10de:1288] (rev a1) 01:00.1 Audio device [0403]: NVIDIA Corporation GK208 HDMI/DP Audio Controller [10de:0e0f] (rev a1) IOMMU group 2 00:14.0 USB controller [0c03]: Intel Corporation 7 Series/C210 Series Chipset Family USB xHCI Host Controller [8086:1e31] (rev 04) IOMMU group 4 [RESET] 00:1a.0 USB controller [0c03]: Intel Corporation 7 Series/C210 Series Chipset Family USB Enhanced Host Controller #2 [8086:1e2d] (rev 04) IOMMU group 5 [RESET] 00:1b.0 Audio device [0403]: Intel Corporation 7 Series/C210 Series Chipset Family High Definition Audio Controller [8086:1e20] (rev 04) IOMMU group 10 [RESET] 00:1d.0 USB controller [0c03]: Intel Corporation 7 Series/C210 Series Chipset Family USB Enhanced Host Controller #1 [8086:1e26] (rev 04) IOMMU group 13 06:00.0 VGA compatible controller [0300]: NVIDIA Corporation GM204 [GeForce GTX 970] [10de:13c2] (rev a1) 06:00.1 Audio device [0403]: NVIDIA Corporation GM204 High Definition Audio Controller [10de:0fbb] (rev a1)
这表明 00:14.0 中的 xHCI USB 控制器无法重置,因此会阻止虚拟机正常关闭,而 00:1b.0 中的集成声卡和 00:1a.0 和 00:1d.0 中的另外两个控制器则没有这个问题,可以无障碍地 passthrough。
完整的设置和示例
出于多种原因,用户可能会寻求查看完整的 passthrough 设置示例。
这些示例是对现有硬件兼容性列表的补充。此外,如果您在配置设置中的某个机制时遇到问题,您可能会发现这些示例非常有价值。用户们在那里详细描述了他们的设置,有些还提供了他们的配置文件示例。
我们鼓励那些成功地从本资源构建系统的人通过贡献他们的构建来帮助改进它。由于涉及许多不同的硬件制造商、似乎严重缺乏充分的文档以及此过程性质的其他问题,社区的贡献是必不可少的。
故障排除
如果您的问题未在下方提及,您可能需要浏览 QEMU#Troubleshooting。
QEMU 4.0:使用 Q35 安装驱动程序后无法加载图形驱动程序/蓝屏/图形卡顿
从 QEMU 4.0 开始,Q35 机器类型将默认的 `kernel_irqchip` 从 `off` 更改为 `split`,这会破坏某些客户机设备,例如 nVidia 图形卡(驱动程序加载失败/黑屏/代码 43/图形卡顿,通常在鼠标移动时发生)。通过在虚拟机配置中 libvirt 的 `<features>` 标签下添加 `<ioapic driver='kvm'/>` 或通过添加 `kernel_irqchip=on` 到 `-machine` QEMU 参数来切换到完整的 KVM 模式。
QEMU 5.0:使用 Zen 2 处理器时,内核版本 5.5 至 5.8.1 的 host-passthrough:Windows 10 蓝屏循环“KERNEL SECURITY CHECK FAILURE”
从 QEMU 5.0 开始,在 Zen 2 处理器和比 5.4 更新的内核上运行的虚拟机将导致蓝屏循环:“KERNEL SECURITY CHECK FAILURE”。可以通过更新到 5.8.2 或更高版本的内核,或禁用 STIBP 来解决此问题。
<cpu mode='host-passthrough' ...> ... <feature policy='disable' name='amd-stibp'/> ... </cpu>
这需要 libvirt 6.5 或更高版本。在旧版本中,有几种变通方法:
- 将 CPU 模式从 `host-passthrough` 切换到 `host-model`。这仅适用于 libvirt 6.4 或更低版本。
- 手动修补 `qemu-desktop` 以撤销此提交。
- 在 qemu 命令行上,将 `amd-stibp=off` 添加到 cpu flags 字符串。这也可以通过 libvirt 的 `<qemu:commandline>` 条目来调用。
移动(Optimus/max-q)Nvidia GPU 出现“错误 43:驱动程序加载失败”
此错误发生是因为 Nvidia 驱动程序想要检查电源供应的状态。如果没有电池,驱动程序将无法工作。无论是 Libvirt 还是 QEMU,默认情况下它们都不提供模拟电池的可能性。这还可能导致屏幕分辨率降低,并且 Nvidia 桌面管理器在右键单击桌面时拒绝加载,并提示需要 Windows 10、兼容的 GPU 和 Nvidia 显卡驱动程序。
但是,您可以创建并添加一个自定义 acpi 表文件到虚拟机,这将完成工作。
首先,您需要通过将以下文件保存为 SSDT1.dat(此处为 base64 编码)来创建自定义 acpi 表文件:
echo 'U1NEVKEAAAAB9EJPQ0hTAEJYUENTU0RUAQAAAElOVEwYEBkgoA8AFVwuX1NCX1BDSTAGABBMBi5f U0JfUENJMFuCTwVCQVQwCF9ISUQMQdAMCghfVUlEABQJX1NUQQCkCh8UK19CSUYApBIjDQELcBcL cBcBC9A5C1gCCywBCjwKPA0ADQANTElPTgANABQSX0JTVACkEgoEAAALcBcL0Dk=' | base64 -d > SSDT1.dat
接下来,您必须将处理后的文件添加到虚拟机的域:
<domain xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0" type="kvm">
...
<qemu:commandline>
<qemu:arg value="-acpitable"/>
<qemu:arg value="file=/path/to/your/SSDT1.dat"/>
</qemu:commandline>
</domain>
确保您的 XML 文件在 `<domain>` 标签中具有正确的命名空间(如上所示),否则 XML 验证将失败。
启动虚拟机后 dmesg 中出现“BAR 3:无法保留 [mem]”错误
关于此文章
如果您仍然遇到代码 43,请在启动虚拟机后检查 `dmesg` 中的内存保留错误,如果您遇到类似情况,可能是因为:
vfio-pci 0000:09:00.0: BAR 3: cannot reserve [mem 0xf0000000-0xf1ffffff 64bit pref]
找出您的显卡连接到的 PCI 桥。这将给出设备的实际层级结构。
$ lspci -t
在启动虚拟机之前,运行以下行,用前面输出中的实际值替换 ID。
# echo 1 > /sys/bus/pci/devices/0000\:00\:03.1/remove # echo 1 > /sys/bus/pci/rescan
另外,尝试添加内核参数 `pci=realloc`,这也有助于热插拔问题。
UEFI (OVMF) VBIOS 兼容性
关于此文章
错误 43 可能由不支持 UEFI 的 GPU VBIOS 引起。要检查您的 VBIOS 是否支持它,您需要使用 `rom-parser`。
$ git clone https://github.com/awilliam/rom-parser $ cd rom-parser && make
转储 GPU VBIOS
# echo 1 > /sys/bus/pci/devices/0000:01:00.0/rom # cat /sys/bus/pci/devices/0000:01:00.0/rom > /tmp/image.rom # echo 0 > /sys/bus/pci/devices/0000:01:00.0/rom
并测试其兼容性
$ ./rom-parser /tmp/image.rom
Valid ROM signature found @600h, PCIR offset 190h PCIR: type 0 (x86 PC-AT), vendor: 10de, device: 1184, class: 030000 PCIR: revision 0, vendor revision: 1 Valid ROM signature found @fa00h, PCIR offset 1ch PCIR: type 3 (EFI), vendor: 10de, device: 1184, class: 030000 PCIR: revision 3, vendor revision: 0 EFI: Signature Valid, Subsystem: Boot, Machine: X64 Last image
要与UEFI兼容,您需要在结果中找到“类型 3 (EFI)”。如果不存在,请尝试更新您的GPU VBIOS。GPU制造商通常会在其支持页面上提供VBIOS升级。一个包含已知兼容且可用的VBIOS(以及它们的UEFI兼容性状态!)的庞大数据库可在TechPowerUp上找到。
更新的VBIOS可以在虚拟机中使用,无需刷新。要在QEMU中加载它
-device vfio-pci,host=07:00.0,......,romfile=/path/to/your/gpu/bios.bin \
以及在libvirt中
<hostdev>
...
<rom file='/path/to/your/gpu/bios.bin'/>
...
</hostdev>
应使用nvflash(Linux版本在“显示更多版本”下)或GPU-Z(在Windows虚拟机中)来比较主机和虚拟机系统之间的VBIOS版本。要检查当前加载的VBIOS
$ ./nvflash --version
... Version : 80.04.XX.00.97 ... UEFI Support : No UEFI Version : N/A UEFI Variant Id : N/A ( Unknown ) UEFI Signer(s) : Unsigned ...
以及检查给定的VBIOS文件
$ ./nvflash --version NV299MH.rom
... Version : 80.04.XX.00.95 ... UEFI Support : Yes UEFI Version : 0x10022 (Jul 2 2013 @ 16377903 ) UEFI Variant Id : 0x0000000000000004 ( GK1xx ) UEFI Signer(s) : Microsoft Corporation UEFI CA 2011 ...
如果外部ROM在虚拟机中未能按预期工作,您将不得不将较新的VBIOS映像刷新到GPU。在某些情况下,可以使用GOPUpd工具创建支持UEFI的VBIOS映像,但这存在风险,可能会导致GPU变砖。
为了避免对显卡造成不可逆的损坏,有必要先卸载NVIDIA内核驱动程序
# modprobe -r nvidia_modeset nvidia
可以使用以下方式刷新VBIOS
# ./nvflash romfile.bin
通过显卡HDMI输出的音频速度变慢
对于一些用户来说,虚拟机通过显卡HDMI输出的音频在一段时间后会变慢/开始卡顿/变得像恶魔般的声音。这通常也会导致图形速度变慢。一个可能的解决方案是启用MSI(消息信号中断)而不是默认的(线路中断)。
要检查MSI是否受支持或已启用,请以root身份运行以下命令
# lspci -vs $device | grep 'MSI:'
其中`$device`是显卡的地址(例如`01:00.0`)。
输出应类似如下
Capabilities: [60] MSI: Enable- Count=1/1 Maskable- 64bit+
`Enable`后面的-表示MSI受支持,但虚拟机未在使用它,而+表示虚拟机正在使用它。
启用它的过程相当复杂,说明和设置概述可以在这里找到。
在Linux虚拟机中,您可以使用modinfo查看是否有关闭MSI的选项(例如:“modinfo snd_hda_intel |grep msi”)。如果有关闭选项,可以通过向自定义omdprobe文件添加相关选项来启用它——在“/etc/modprobe.d/snd-hda-intel.conf”中插入“options snd-hda-intel enable_msi=1”
其他提示可以在lime-technology的wiki[链接已死 2023-05-06—HTTP 404],或在VFIO技巧与窍门这篇文章中找到。
一个名为MSI Utility (FOSS Version 2)的UI工具可与Windows 10 64位配合使用,并简化了此过程。
为了解决在NVIDIA显卡的0个功能上启用MSI的问题(01:00.0 VGA compatible controller: NVIDIA Corporation GM206 [GeForce GTX 960] (rev a1) (prog-if 00 [VGA controller]))是不够的;还需要在其另一个功能(01:00.1 Audio device: NVIDIA Corporation Device 0fba (rev a1))上启用它才能解决问题。
启用intel_iommu后,主机上没有HDMI音频输出
如果在启用intel_iommu后,Intel GPU的HDMI输出设备在主机上无法使用,则设置igfx_off选项(即intel_iommu=on,igfx_off)可能会恢复音频,请阅读iommu.html以了解有关设置igfx_off的详细信息。
启用vfio_pci后X无法启动
这与主机GPU被检测为辅助GPU有关,这会导致X尝试加载虚拟机GPU的驱动程序时失败/崩溃。为了规避此问题,需要一个指定主机GPU总线ID的Xorg配置文件。正确的总线ID可以从lspci -n或Xorg日志[7]中获取。请注意,lspci输出中的值是十六进制的,应在.conf文件中转换为十进制。
/etc/X11/xorg.conf.d/10-intel.conf
Section "Device"
Identifier "Intel GPU"
Driver "modesetting"
BusID "PCI:0:2:0"
EndSection
Chromium忽略集成显卡进行加速
Chromium及其相关程序会尝试检测系统中尽可能多的GPU,并选择首选的(通常是独立的NVIDIA/AMD显卡)。它尝试通过查看PCI设备而不是系统中可用的OpenGL渲染器来选择GPU,其结果是Chromium可能会忽略用于渲染的集成GPU,并尝试使用绑定到vfio-pci驱动程序的专用GPU,而该GPU在主机系统上不可用,无论虚拟机是否正在运行。这导致使用软件渲染(导致CPU负载增加,也可能导致视频播放、滚动和总体平滑度不佳)。
这可以通过明确告诉Chromium您想使用哪个GPU来解决。
虚拟机只使用一个核心
对于一些用户来说,即使启用了IOMMU并将核心数设置为大于1,虚拟机仍然只使用一个CPU核心和线程。为了解决这个问题,请在virt-manager中启用“手动设置CPU拓扑”,并将其设置为所需的CPU插槽、核心和线程数量。请记住,“线程”是指每个CPU的线程数,而不是总线程数。
直通似乎有效但没有输出显示
如果您使用的是virt-manager,请确保为虚拟机选择了UEFI固件。同时,请确保您已将正确的设备传递给了虚拟机。
虚拟机关闭后主机锁定
此问题主要影响运行Windows 10虚拟机的用户,通常在虚拟机运行一段时间后:主机将经历多个CPU核心锁定(请参阅[8])。为了解决这个问题,请尝试在传递给虚拟机的GPU上启用消息信号中断。一个好的操作指南可以在[9]中找到。您也可以在此处下载此Windows应用程序[10],它应该会使过程更轻松。
虚拟机在睡眠期间运行导致主机锁定
VFIO启用的虚拟机在跨越睡眠/唤醒周期运行时往往会变得不稳定,并且已知会导致在尝试关闭虚拟机时主机机器锁定。为了避免这种情况,可以使用以下libvirt钩子脚本和systemd单元,简单地阻止主机在虚拟机运行时进入睡眠。钩子文件需要可执行权限才能工作。
/etc/libvirt/hooks/qemu
#!/bin/sh
OBJECT="$1"
OPERATION="$2"
SUBOPERATION="$3"
EXTRA_ARG="$4"
case "$OPERATION" in
"prepare")
systemctl start libvirt-nosleep@"$OBJECT"
;;
"release")
systemctl stop libvirt-nosleep@"$OBJECT"
;;
esac
/etc/systemd/system/libvirt-nosleep@.service
[Unit] Description=Preventing sleep while libvirt domain "%i" is running [Service] Type=simple ExecStart=/usr/bin/systemd-inhibit --what=sleep --why="Libvirt domain \"%i\" is running" --who=%U --mode=block sleep infinity
自Windows 10 1803以来,启动时出现蓝屏
自Windows 10 1803以来,在使用“host-passthrough”作为CPU模型时出现了一个问题。机器无法启动,要么处于启动循环,要么出现蓝屏。您可以采取以下措施来解决这个问题:
# echo 1 > /sys/module/kvm/parameters/ignore_msrs
为了使其永久生效,您可以创建一个modprobe文件kvm.conf
options kvm ignore_msrs=1
为了防止dmesg被“ignored rdmsr”消息淹没,您还可以添加
options kvm report_ignored_msrs=0
AMD Ryzen / BIOS更新(AGESA)导致“错误:内部错误:未知PCI头类型‘127’”
AMD用户在更新主板BIOS后遇到了KVM设置损坏的问题。有一个内核补丁[链接已死 2024-07-30—HTTP 404](有关编译带自定义补丁的内核的说明,请参阅Kernel/Arch build system),目前(19年7月28日)可以解决此问题,但这并非AMD首次犯此类错误,因此如果您作为VFIO用户考虑将来更新BIOS,请考虑这一点。
AMD GPU未正确重置,导致“错误:内部错误:未知PCI头类型‘127’” (与上述问题分开)
直通AMD GPU可能会遇到称为“AMD重置bug”的问题。在虚拟机重启后,GPU未能正确重置其状态,导致设备故障,直到主机也重启。这通常伴随着Windows虚拟机中的“代码43”驱动程序错误,以及主机libvirt日志中的“错误:内部错误:未知PCI头类型‘127’”消息。
过去,这意味着必须使用变通方法手动重置GPU,或使用不太可能合并到主线的内核补丁。目前,不需要修补内核的推荐解决方案是安装vendor-reset-gitAUR或vendor-reset-dkms-gitAUR,并确保在启动虚拟机之前加载“vendor-reset”内核模块。为了方便起见,您可以自动加载模块。
热插拔带USB的NVIDIA卡时主机崩溃
如果尝试带USB端口热插拔NVIDIA卡,您可能需要将i2c_nvidia_gpu驱动程序列入黑名单。通过将blacklist i2c_nvidia_gpu行添加到/etc/modprobe.d/blacklist.conf中来实现。
启用vfio后主机无法启动并卡在黑屏
如果在启动时通过添加debug ignore_loglevel内核参数启用了调试内核消息,您可能会看到启动卡住,最后一条消息类似如下
vfio-pci 0000:01:00.0: vgaarb: changed VGA decodes: olddecodes=io+mem,decodes=io+mem:owns=none
这可以通过断开直通GPU与显示器的连接来缓解。主机启动后,您可以重新将直通GPU连接到显示器。
如果您不想每次启动主机时都插拔线缆。您可以禁用引导加载程序中的帧缓冲以绕过此消息。对于UEFI系统,您可以添加video=efifb:off作为内核参数。对于旧版支持,请使用video=vesafb:off,或者同时使用。请注意,这样做可能会导致Xorg出现问题。
如果您遇到Xorg的问题,以下解决方案可能会有所帮助(请记住根据需要替换为您自己的值)。
/etc/X11/xorg.conf.d/10-amd.conf
Section "Device"
Identifier "AMD GPU"
Driver "amdgpu"
BusID "PCI:0:2:0"
EndSection
直通PCIe USB集线器时出现AER错误
在某些情况下,直通PCIe USB集线器(例如连接到虚拟机GPU的集线器)可能会失败并出现类似以下的AER错误
kernel: pcieport 0000:00:01.1: AER: Uncorrected (Non-Fatal) error received: 0000:00:01.1 kernel: pcieport 0000:00:01.1: AER: PCIe Bus Error: severity=Uncorrected (Non-Fatal), type=Transaction Layer, (Requester ID) kernel: pcieport 0000:00:01.1: AER: device [8086:1905] error status/mask=00100000/00000000 kernel: pcieport 0000:00:01.1: AER: [20] UnsupReq (First) kernel: pcieport 0000:00:01.1: AER: TLP Header: 00000000 00000000 00000000 00000000 kernel: pcieport 0000:00:01.1: AER: device recovery successful
保留内存区域报告(RMRR)冲突
如果您遇到由于BIOS使用RMRR而无法直通设备的问题,例如以下错误。
vfio-pci 0000:01:00.1: Device is ineligible for IOMMU domain attach due to platform RMRR requirement. Contact your platform vendor.
您可以尝试此处的补丁:https://github.com/kiler129/relax-intel-rmrr
直通到虚拟机的AMD GPU频率限制过低
在某些配备AMD GPU的机器上,将设备绑定到vfio-pci可能不足以阻止主机干扰,因为主机上的amdgpu驱动程序可能会查询全局ATIF方法,从而改变GPU的行为。例如,一位用户报告说,在配备Radeon Pro WX 3200 AMD GPU的Dell Precision 7540笔记本电脑上,当AMD GPU绑定到vfio-pci时,直通的AMD GPU的频率被限制在501 MHz,而不是正确的1295 MHz限制。使用内核命令行黑名单amdgpu内核模块是一个变通方法。
有关更多详细信息,请参阅此内核邮件列表讨论。
主机时钟源是HPET而不是TSC
如果您的主机时钟源是HPET,您可能会在游戏中看到性能严重下降。虽然此问题在虚拟机外运行游戏时也会发生,但不知情的用户很可能会开始排查他们的虚拟机,而问题实际上是他们的主机系统。
要查看您当前的时钟源,请在您的主机上运行此命令
$ cat /sys/devices/system/clocksource/clocksource*/current_clocksource
如果您的时钟源是HPET,您可以尝试通过设置内核参数clocksource=tsc和tsc=reliable将其强制设为TSC。有关更多信息,请参阅此Reddit帖子。
启用BIOS中的Resizable Bar后出现代码43
如果您拥有支持Resizable BAR / SAM的GPU并且已启用相应的BIOS选项,您可能会因为QEMU中禁用了此功能而遇到代码43[11]。Linux Kernel 6.1版本通过sysfs增加了操作PCIe Resizable BAR的选项,因此您可以尝试使用udev规则永久调整其大小
/etc/udev/rules.d/01-amd.rules
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x1002", ATTR{device}=="0x73bf", ATTR{resource0_resize}="14"
ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x1002", ATTR{device}=="0x73bf", ATTR{resource2_resize}="8"
或者,要临时调整PCIe BAR的大小,您可以将新大小写入/sys/bus/pci/devices/device_address/resourcenumber_resize,其中device_address是您GPU的地址,例如0000:03:00.0(请注意,要更改此参数,不能为设备加载任何驱动程序)。
您可以使用lspci -vvvxxxx确认值。
AMD图形处理器可能需要为BAR 2设置一个特定值,即8 MB,尽管最大支持值为256 MB。然而,此要求可能取决于多个因素,例如您系统上的QEMU版本。如果您已经用尽了其他选项来防止错误代码43,请将该值设置为3(在resource2_resize文件中),这将强制BAR 2设置为8 MB。
操作系统挂起
如果您的虚拟化操作系统(尤其是Windows)在附加GPU时挂起,您也可以尝试打开主板设置并
- 禁用
Re-Size BAR Support, - 禁用
Above 4G memory / Crypto Currency mining, - 启用
SR-IOV, - 将
Max TOLUD从dynamic更改为特定值, - 将
Initiate Graphic Adapter从PEG切换到IGD。
至少,VEGA显卡似乎对这些设置很挑剔,除非进行更改否则不会实际工作。
虚拟机运行游戏时GPU超时
从最近的内核(AMD为6.12+,Intel为5.x)开始,拆分锁检测默认启用。这会导致创建拆分锁的进程强制暂停10毫秒,可能导致GPU超时和类似的虚拟机挂起。运行以下命令可以禁用此行为
# sysctl -w kernel.split_lock_mitigate=0
vfio: DMA映射失败,无法继续
启动虚拟机时,可能会出现类似“qemu: hardware error: vfio: DMA mapping failed, unable to continue”的错误,并阻止VM启动。要解决此问题,请将以下内容添加到XML文件中:[12]
<cpu> <maxphysaddr mode='passthrough' limit='40'/> </cpu>
如果直接使用QEMU,请使用-cpu host,host-phys-bits-limit=0x28。
也有一些报告称VM仍然无法启动;尝试递减数字(即,对libvirt使用limit='39',对QEMU使用host-phys-bits-limit=0x27)。
vIOMMU:无法设置vIOMMU:aw-bits X > host aw-bits Y
启动虚拟机时,可能会出现类似以下内容的错误
libvirt.libvirtError: internal error: QEMU unexpectedly closed the monitor (vm='win11'): 2025-08-30T21:38:26.333149Z qemu-system-x86_64: -device {"driver":"vfio-pci","host":"0000:03:00.0","id":"hostdev0","bus":"pci.5","addr":"0x0"}: vfio 0000:03:00.0: Failed to set vIOMMU: aw-bits 48 > host aw-bits 39
将XML中的以下部分添加caching_mode="on" aw_bits="39"或相关的比特数。
<devices>
... other entries ...
<iommu model="intel">
<driver intremap="on" caching_mode="on" eim="on" aw_bits="39"/>
</iommu>
</devices>
更多信息