PCI 直通 via OVMF
开放虚拟机固件 (OVMF) 是一个为虚拟机启用 UEFI 支持的项目。从 Linux 3.9 和最新版本的 QEMU 开始,现在可以直通显卡,为虚拟机提供原生图形性能,这对于图形密集型任务非常有用。
如果您有一台台式电脑,配备可以专用于主机的备用 GPU(无论是集成 GPU 还是旧的 OEM 卡,品牌甚至不需要匹配),并且您的硬件支持它(请参阅#先决条件),则可以拥有任何操作系统的虚拟机,它有自己的专用 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。
- 如果您可以在此列表中的任何 ROM中找到适用于您的特定 GPU 并且据说支持 UEFI 的 ROM,那么通常就没问题。2012 年及之后的所有 GPU 都应支持此功能,因为微软已将 UEFI 作为设备以 Windows 8 兼容性进行销售的必要条件。
您可能需要备用显示器或具有连接到不同 GPU 的多个输入端口的显示器(如果没有插入屏幕,直通 GPU 将不会显示任何内容,并且使用 VNC 或 Spice 连接将无助于您的性能),以及可以传递给虚拟机的鼠标和键盘。如果出现任何问题,您至少可以通过这种方式控制您的主机。
设置 IOMMU
- IOMMU 是 Intel VT-d 和 AMD-Vi 的通用名称。
- VT-d 代表定向 I/O 的 Intel 虚拟化技术,不应与 VT-x Intel 虚拟化技术混淆。VT-x 允许一个硬件平台充当多个“虚拟”平台,而 VT-d 提高了系统的安全性和可靠性,并且还提高了虚拟化环境中 I/O 设备的性能。
使用 IOMMU 可以实现 PCI 直通和内存保护等功能,以防止故障或恶意设备,请参阅 Wikipedia:Input-output memory management unit#优点 和 内存管理 (计算机编程): 您能用简单的英语解释一下 IOMMU 吗?。
启用 IOMMU
确保您的 CPU 支持 AMD-Vi 或 Intel VT-d,并在 BIOS 设置中启用。这些选项通常与其他 CPU 功能一起出现,这些功能可能位于与超频相关的菜单中。它们可能会以实际名称(“VT-d”或“AMD-Vi”)列出,或者以更模棱两可的术语(例如“虚拟化技术”)列出,主板手册中可能会或可能不会对此进行解释。
使用 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 覆盖补丁,但这会带来自身的缺点。有关更多信息,请参阅#绕过 IOMMU 组(ACS 覆盖补丁)。
隔离 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 对来指定要隔离的设备。如果属于这种情况,请改为阅读 #使用相同的客户机和主机 GPU。
- 如果在 #将客户机 GPU 插入未隔离的基于 CPU 的 PCIe 插槽 中指出,您的 pci 根端口是 IOMMU 组的一部分,则您不得将其 ID 传递给
vfio-pci
,因为它需要保持连接到主机才能正常运行。但是,该组中的任何其他设备都应留给vfio-pci
绑定。 - 绑定音频设备(上面示例中的
10de:0fbb
)是可选的。Libvirt 能够自行将其从snd_hda_intel
驱动程序中解除绑定。
提供设备 ID 是通过 内核模块参数 ids=10de:13c2,10de:0fbb
为 vfio-pci
完成的。
如果想要将 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 镜像中。如果通过内核参数给出,则读取时间太晚而无法生效。请按照 #通过设备 ID 绑定 vfio-pci 中的说明将 id 添加到 modprobe 配置文件。
此外,请确保 modconf 挂钩包含在 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#早期内核模块加载)。
与 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)
并非所有设备(甚至预期的设备)都必须在 dmesg 输出中来自 vfio.conf
。即使设备未出现,它可能仍然在客户机虚拟机中可见和可用。
$ 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 方法,则通常最好使用 EFI 方法。
配置 libvirt
Libvirt 是许多虚拟化实用程序的包装器,它极大地简化了虚拟机的配置和部署过程。在 KVM 和 QEMU 的情况下,它提供的前端使我们能够避免处理 QEMU 的权限,并使在活动的虚拟机上添加和删除各种设备变得更加容易。但是,它作为包装器的地位意味着它可能并不总是支持所有最新的 qemu 功能,这最终可能需要使用包装脚本来为 QEMU 提供一些额外的参数。
安装 qemu-desktop、libvirt、edk2-ovmf 和 virt-manager。对于默认网络连接,需要 dnsmasq。
按照 Libvirt#配置 配置 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 磁盘
其余安装过程将像往常一样使用在窗口中运行的标准 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 主机设备绑定到您的虚拟机,但请记住至少保留一个分配给您的主机的鼠标和/或键盘,以防客户机出现问题。这可以通过使用 添加硬件 > USB 主机设备
来完成。
此时,也可以连接之前隔离的 PCI 设备;只需单击“添加硬件”,然后选择您要直通的 PCI 主机设备。如果一切顺利,插入 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
替换为您的设备路径。 现在您可以启动访客操作系统,并通过同时按下左右 Ctrl 键来测试在主机和访客系统之间切换鼠标和键盘的控制权。
您还可以考虑在配置中从 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 镜像来确定您是否拥有无效的介质。
性能调优
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
在 6c/12t 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。 绑定和隔离少于这些 CPU(例如 8-11)将导致主机系统使用核心 6 和 7 中的 L3 缓存,最终将导致缓存驱逐,从而导致性能下降。
在 6c/12t 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 的特定硬件特性,但是 Core 0 在大多数情况下是主机的不错选择。 如果为主机保留任何核心,则建议将模拟器和 iothread(如果使用)绑定到主机核心,而不是 VCPU。 这可以提高访客系统的性能并减少延迟,因为这些线程不会污染缓存或与访客 VCPU 线程争夺调度。 如果所有核心都传递给访客系统,则无需也无益于绑定模拟器或 iothread。
XML 示例
4c/1t 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> ...
4c/2t 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> ...
4c/2t 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 实例。 QEMU 的 PID 必须在 grep 命令中替换
$ 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 巨页的机器上使用 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 参数手动分配巨页。
/etc/sysctl.d/10-kvm.conf
vm.nr_hugepages = 0 vm.nr_overcommit_hugepages = num
其中 num
- 是巨页的数量,默认大小为 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 频率,如果 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”等。
有关更多信息,请参阅 Reddit 线程的 Removeddit 镜像的 Internet Archive 副本。
动态隔离 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 会自动被主机充分利用。
以下 reddit 线程中包含更多示例:[1] [2] [3]
请注意,这需要 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 模拟。 这些控制器提供最大的兼容性,但不适合高效虚拟化。 存在两种加速模型:用于 SCSI 模拟和直通的 virtio-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 cdrom 连接
- 要在现有的 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 zvol 或 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>
创建 iothread
<domain> <iothreads>1</iothreads>
绑定 iothread
<domain> <cputune> <iothreadpin iothread='1' cpuset='0-1,6-7'/>
带有 virt-manager 的示例
这将创建一个 virtio-blk
设备
- 打开虚拟机首选项
- 转到
添加硬件 > 存储
- 创建或选择存储文件
- 选择
设备类型:磁盘设备
和总线类型:VirtIO
- 单击完成
Virtio 网络
默认 NIC 模型 rtl8139 或 e1000 可能是千兆位以上速度的瓶颈,并且与 virtio-net
相比具有大量的 CPU 开销。
- 使用 libvirt 选择
virtio
作为 NIC 的模型,或在裸 qemu 中使用virtio-net-pci
设备 - Windows 需要 Windows virtio 驱动程序 中的
NetKVM
驱动程序 - Virtio 默认使用 vhost-net 进行内核内数据包处理,而无需退出到用户空间
- 可以启用多队列以进一步加速多个连接,但通常不会提高单流速度。 对于 libvirt,在接口标记下添加
<driver queues='8'/>
- 也可以通过设置模块参数
vhost_net.experimental_zcopytx=1
在 macvtap 上启用零拷贝传输,但这实际上可能会导致更差的性能,请参阅 commit
带有桥接的 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”。 红帽提供了包含差异描述的指南[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 对来识别它们需要在启动时绑定的设备,如果您有两个 GPU 共享这样的 ID 对,您将无法仅将直通驱动程序绑定到其中一个。 这种设置使得必须使用脚本,以便您正在使用的任何驱动程序都通过 pci 总线地址使用 driver_override
机制进行分配。
脚本变体
直通除启动 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
直通选定的 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 的 IOMMU 组
简化从选定 GPU 直通其他必要设备。 例如显卡的板载音频、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 直通时是一个特殊情况,因为 BIOS 需要使用它来显示启动消息或 BIOS 配置菜单等内容。 为此,它 复制了 VGA 启动 ROM,然后可以自由修改。 此修改后的副本是系统看到的版本,直通驱动程序可能会拒绝该版本,因为它无效。 因此,通常建议在 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> ...
您应该根据您要直通的分辨率,将 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 设备的信息,它会静默安装一个虚拟驱动程序。 要实际启用该设备,您必须进入设备管理器,并更新“系统设备”节点下 “PCI 标准 RAM 控制器” 的设备的驱动程序。 从 Red Hat 下载已签名的驱动程序。
驱动程序安装完成后,您必须下载与您将从 AUR 安装的客户端匹配的 looking-glass-host 软件包,并将其安装在您的虚拟机客户机上。为了运行它,您还需要从 Microsoft 安装 Microsoft Visual C++ Redistributable。最新版本将自动安装一项服务,该服务在启动时启动守护程序。主机守护程序的日志位于客户机系统上的 %ProgramData%\Looking Glass (host)\looking-glass-host.txt
。
设置空视频设备
(检索自:https://looking-glass.io/docs/stable/install/#spice-server)
如果您想使用 Spice 来为您提供键盘和鼠标输入以及剪贴板同步支持,请确保您拥有 <graphics type='spice'>
设备,然后
- 找到您的
<video>
设备,并将<model type='none'/>
设置为 - 如果您找不到它,请确保您拥有
<graphics>
设备,保存并再次编辑
获取客户端
可以使用 AUR 中的 looking-glassAUR 或 looking-glass-gitAUR 软件包安装 Looking Glass 客户端。
虚拟机设置并运行后,您可以启动它
$ looking-glass-client
如果您不想使用 Spice 来控制客户机鼠标和键盘,您可以禁用 Spice 服务器。
$ looking-glass-client -s
此外,您可能希望全屏启动 Looking Glass 客户端,否则图像可能会缩小,导致图像保真度降低。
$ looking-glass-client -F
要禁止主机屏幕保护程序,请使用 -S
标志启动 Looking Glass。
$ looking-glass-client -S
这在某些 DE(包括 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 客户机上的鼠标移动。但是,对于某些应用程序(例如游戏),这可能会有太多的延迟。另一种方法是传递特定的 USB 设备以实现最小的延迟。这允许在主机和客户机之间切换设备。
首先,为您希望传递的设备创建一个 .xml 文件,libvirt 将使用该文件来识别设备。
~/.VFIOinput/input_1.xml
<hostdev mode='subsystem' type='usb'> <source> <vendor id='0x[Before Colon]'/> <product id='0x[After Colon]'/> </source> </hostdev>
将 [冒号前/冒号后] 替换为 'lsusb' 命令的内容,特定于您要传递的设备。
例如,我的鼠标是 Bus 005 Device 002: ID 1532:0037 Razer USA, Ltd
,所以我将用 1532 替换 vendor id
,用 0037 替换 product id
。
为您要传递的任何其他 USB 设备重复此过程。如果您的鼠标/键盘在 lsusb
中有多个条目,例如它是无线的,则为每个条目创建额外的 xml 文件。
接下来,需要一个 bash 脚本文件来告诉 libvirt 将 USB 设备附加/分离到客户机。
~/.VFIOinput/input_attach.sh
#!/bin/sh virsh attach-device [VirtualMachine-Name] [USBdevice]
将 [虚拟机名称] 替换为您的虚拟机名称,该名称可以在 virt-manager 下看到。此外,将 [USB设备] 替换为您要传递的设备的 .xml 文件的完整路径。为多个设备添加额外的行。例如,这是我的脚本
~/.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 设备与您不想传递的其他设备分组在一起,您可以使用 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=
选项的新内核。
不使用 libvirt 的纯 QEMU
可以使用带有自定义参数的纯 QEMU 命令来运行虚拟机,而不是借助 libvirt 设置虚拟机,该虚拟机旨在与 PCI 直通一起使用。这对于某些用例(如脚本化设置)是理想的,在这些用例中,需要与其他脚本一起使用的灵活性。
为了实现这一点,在 #设置 IOMMU 和 #隔离 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 固件二进制文件,注意只读选项-drive if=pflash,format=raw,file=/tmp/my_OVMF_VARS.4m.fd
用于变量
- 确保在
my_OVMF_VARS.4m.fd
之前给出OVMF_CODE.4m.fd
作为命令行参数。否则,引导顺序将失败。 - 可以使用 QEMU 的默认 SeaBIOS 代替 OVMF,但不建议这样做,因为它可能会导致直通设置出现问题。
建议研究 QEMU 文章,了解如何通过使用 virtio 驱动程序 和其他进一步的配置来增强性能。
您可能还需要使用 -cpu host,kvm=off
参数将主机的 CPU 型号信息转发到虚拟机,并欺骗 Nvidia 的虚拟化检测以及可能其他制造商的设备驱动程序,这些驱动程序试图阻止虚拟化系统内部的完整硬件使用。
传递其他设备
USB 控制器
如果您的主板有多个 USB 控制器映射到多个组,则可以传递这些控制器而不是 USB 设备。传递实际控制器而不是单个 USB 设备具有以下优点
- 如果设备在给定操作过程中断开连接或更改 ID(例如手机正在进行更新),虚拟机将不会突然停止看到它。
- 由此控制器管理的任何 USB 端口都由虚拟机直接处理,并且可以拔下、重新插入和更改其设备,而无需通知虚拟机监控程序。
- 当启动虚拟机时,如果您通常传递给客户机的 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 管理。
一旦您通过将各种设备插入其中并确定您想要直通的控制器来识别哪个控制器管理哪些端口,只需将其添加到客户机配置中虚拟机控制的 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>
(请根据您的桌面用户 uid 将 runtimeDir 值从 1000 更改为相应的值)
要解决“Failed to initialize PW context”错误,您可以修改 qemu 配置以使用您的用户。
/etc/libvirt/qemu.conf
user = "example"
通过 PulseAudio 将音频从虚拟机传递到主机
可以使用 libvirt 将虚拟机的音频作为应用程序路由到主机。这样做的好处是可以将多个音频流路由到一个主机输出,并且可以使用不支持直通的音频输出设备。这需要 PulseAudio 在主机系统上运行。
首先,安装 qemu-audio-pa。
然后,删除 #user = ""
行的注释。然后在引号中添加您的用户名。这告诉 QEMU 要通过哪个用户的 pulseaudio 流进行路由。
/etc/libvirt/qemu.conf
user = "example"
模拟音频设置由两个组件组成:暴露给客户机的模拟声音设备和将声音设备连接到主机 PulseAudio 的音频后端。
在可用的模拟声音设备中,主要关注两个:ICH9 和 usb-audio。ICH9 同时具有输出和输入功能,但仅限于立体声。usb-audio 仅具有音频输出功能,但支持高达 6 声道的 5.1 配置。对于 ICH9,删除 <devices>
部分中任何预先存在的音频后端并添加
$ 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 中多次指定
<audio>
/-audiodev
,并为它们分配不同的 id 即可。这对于拥有两个相同后端的用例非常有用。使用 PulseAudio,每个后端都是一个单独的流,可以路由到主机上的不同输出设备(使用像 pavucontrol 或 pulsemixer 这样的脉冲混音器)。 - Libvirt/QEMU 中需要 USB 3 模拟才能启用 usb-audio。
- 建议使用诸如 [6] 之类的工具在 ICH9 音频设备上启用 MSI 中断,以减轻虚拟机重启后可能出现的任何噼啪声、断断续续、加速或完全没有音频的情况。
- 如果音频仍然存在噼啪声/断断续续/加速等问题,您可能需要调整诸如
buffer-length
和timer-period
之类的参数,有关这些参数和更多参数的更多信息,请参见 qemu(1) 手册。 - 某些音频芯片组(例如 Realtek alc1220)也可能存在开箱即用的问题,因此在使用 QEMU 进行任何音频模拟时,请考虑这一点。
- 不正确的引脚分配或主机大量使用而未使用 isolcpus 也会影响声音错误,尤其是在虚拟机中玩游戏时。
通过 JACK 和 PipeWire 将音频从虚拟机传递到主机
也可以通过 JACK 和 PipeWire 将虚拟机的音频传递到主机。
首先,确保您有一个可以工作的 PipeWire 设置,并具有 JACK 支持。
接下来,您需要告诉 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 将音频从虚拟机传递到主机
可以使用名为 Scream 的应用程序,通过桥接网络(例如 Libvirt 提供的网络)或通过向主机添加 IVSHMEM 设备来传递虚拟机的音频。本节仅介绍在主机上使用 PulseAudio 作为接收器。有关其他方法的更多详细信息和说明,请参阅项目页面。
将 Scream 与桥接网络一起使用
- 这是使用此方法的首选方式,尽管结果可能因用户而异
- 建议在使用 Scream 时使用 #Virtio 网络适配器,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(建议量)。根据需要更改此值。
现在参考 #将 IVSHMEM 设备添加到虚拟机 以配置主机以在启动时创建共享内存文件,将 looking-glass
替换为 scream-ivshmem
。
为 IVSHMEM 配置 Windows 客户机
必须在客户机上为 IVSHMEM 设备安装正确的驱动程序。请参阅 #将 IVSHMEM 主机安装到 Windows 客户机。忽略有关 looking-glass-host
的部分。
在客户机上安装 Scream 虚拟音频驱动程序。如果您的虚拟机启用了安全启动,您可能需要禁用它。
使用注册表编辑器,将 DWORD HKLM\SYSTEM\CurrentControlSet\Services\Scream\Options\UseIVSHMEM
设置为 IVSHMEM 设备的大小(以 MB 为单位)。请注意,scream 使用其大小来识别其 IVSHMEM 设备,因此请确保只有一个该大小的设备(建议的默认值为 2
,表示 2MB)。
在管理员 CMD shell 中使用以下命令创建键和 DWORD:REG ADD HKLM\SYSTEM\CurrentControlSet\Services\Scream\Options /v UseIVSHMEM /t REG_DWORD /d 2
(来源于 Github 上的 scream)
配置主机
安装 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
使用 IVSHMEM 设备的大小(以 MiB 为单位)编辑 count=2
。
pulseaudio.service
替换为 pipewire-pulse.service
,并添加 After=wireplumber.service
。现在启动 scream-ivshmem-pulse.service
用户单元。
物理磁盘/分区
原始格式和 qcow2 格式尤其在繁重的 IO 操作中可能会有明显的开销。可以直接使用整个磁盘或分区来绕过文件系统并提高 I/O 性能。如果您希望原生双启动客户机操作系统,您需要传递整个磁盘,而无需任何分区。建议使用 /dev/disk/by-paths 来引用磁盘,因为 /dev/sdX 条目可能会在启动之间更改。要找出哪个磁盘/分区与您想要传递的磁盘/分区相关联
$ ls -l /dev/disk/by-id/*
/dev/disk/by-id/ata-ST1000LM002-9VQ14L_Z0501SZ9 -> ../../sdd
请参阅 #Virtio 磁盘,了解如何在 libvirt XML 中添加这些磁盘。您也可以使用 Virt-Manager 的 添加硬件 菜单添加磁盘,然后在 选择或创建自定义存储 框中键入您想要的磁盘,例如 /dev/disk/by-id/ata-ST1000LM002-9VQ14L_Z0501SZ9
注意事项
传递不支持重置的设备
当虚拟机关闭时,客户机操作系统会取消初始化客户机使用的所有设备,以准备关闭。在这种状态下,这些设备不再起作用,必须先断电重启,然后才能恢复正常运行。Linux 可以自行处理此断电重启,但是当设备没有已知的重置方法时,它将保持禁用状态并变得不可用。由于 Libvirt 和 Qemu 都期望所有主机 PCI 设备都准备好在完全停止虚拟机之前重新连接到主机,因此当遇到无法重置的设备时,它们将挂起在“正在关闭”状态,在这种状态下,除非主机系统已重新启动,否则它们将无法重新启动。因此,建议仅传递内核能够重置的 PCI 设备,如 PCI 设备 sysfs 节点(例如 /sys/bus/pci/devices/0000:00:1a.0/reset
)中是否存在 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 中的其他两个控制器没有此问题,可以无问题地传递。
完整的设置和示例
出于多种原因,用户可能希望查看 完整的直通设置示例。
这些示例是对现有硬件兼容性列表的补充。此外,如果您在设置中配置某个机制时遇到问题,您可能会发现这些示例非常有价值。那里的用户详细描述了他们的设置,有些人还提供了他们的配置文件示例。
我们鼓励那些从本资源成功构建系统的人通过贡献他们的构建来帮助改进它。由于涉及许多不同的硬件制造商,看似严重缺乏足够的文档,以及由于此过程的性质而产生的其他问题,社区贡献是必要的。
故障排除
如果您的Issue未在下面提及,您可能需要浏览 QEMU#故障排除。
QEMU 4.0:使用 Q35 安装驱动程序后,无法加载图形驱动程序/BSOD/图形卡顿
从 QEMU 4.0 开始,Q35 机器类型将默认 kernel_irqchip
从 off
更改为 split
,这会破坏某些客户机设备,例如 nVidia 显卡(驱动程序无法加载/黑屏/代码 43/图形卡顿,通常在移动鼠标时)。通过在 libvirt 的虚拟机配置中的 <features>
标签下添加 <ioapic driver='kvm'/>
或在 -machine
QEMU 参数中添加 kernel_irqchip=on
,切换到完整的 KVM 模式。
QEMU 5.0:使用内核版本 5.5 到 5.8.1 的主机直通,当使用 Zen 2 处理器时:Windows 10 BSOD 循环“KERNEL SECURITY CHECK FAILURE”
从 QEMU 5.0 开始,在 Zen 2 和高于 5.4 的内核上运行的虚拟机将导致 BSOD 循环:“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 标志字符串。也可以通过 libvirt 通过<qemu:commandline>
条目调用此选项。
使用移动版 (Optimus/max-q) nvidia GPU 时出现“Error 43: Driver failed to load”
发生此错误是因为 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: cannot reserve [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
,它也 有助于热插拔问题。
VBIOS 中的 UEFI (OVMF) 兼容性
关于 这篇文章
错误 43 可能是由 GPU 的 VBIOS 没有 UEFI 支持引起的。要检查您的 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 兼容性,您需要在结果中获得“type 3 (EFI)”。如果那里没有,请尝试更新您的 GPU VBIOS。GPU 制造商通常在其支持页面上共享 VBIOS 升级。在 TechPowerUp 上可以找到一个大型的已知兼容且可用的 VBIOS 数据库(以及它们的 UEFI 兼容性状态!)。
更新后的 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,但虚拟机没有使用;+
表示虚拟机正在使用 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 ⓘ], 或这篇关于 VFIO 技巧和窍门 的文章中找到。
一个名为 MSI Utility (FOSS Version 2) 的 UI 工具适用于 Windows 10 64 位系统,并简化了此过程。
为了解决问题,仅仅在 nVidia 显卡的 0 功能 (01:00.0 VGA compatible controller: NVIDIA Corporation GM206 [GeForce GTX 960] (rev a1) (prog-if 00 [VGA controller])
) 上启用 MSI 是不够的;还需要在另一个功能 (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 加载驱动程序时,会导致 X 失败/崩溃。 为了避免这种情况,需要一个 Xorg 配置文件,其中指定主机 GPU 的 BusID。 可以从 lspci -n
或 Xorg 日志 [7] 中获取正确的 BusID。 请注意,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,并选择首选的 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 上启用消息信号中断 (MSI)。 关于如何执行此操作的良好指南可以在 [9] 中找到。 你也可以在这里下载这个 Windows 应用程序 [10],它应该使过程更简单。
如果虚拟机在睡眠期间保持运行,则主机死锁
如果 VFIO 启用的虚拟机在睡眠/唤醒周期中保持运行,则往往会变得不稳定,并且已知在尝试关闭它们时会导致主机死锁。 为了避免这种情况,可以使用以下 libvirt hook 脚本和 systemd 单元来简单地阻止主机在虚拟机运行时进入睡眠状态。 hook 文件需要可执行权限才能工作。
/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) 导致 “Error: internal error: Unknown PCI header type ‘127’” 错误
AMD 用户在更新主板上的 BIOS 后,一直遇到 KVM 设置崩溃的问题。 有一个内核补丁[死链接 2024-07-30 ⓘ],(有关使用自定义补丁编译内核的说明,请参阅 Kernel/Arch build system)目前(2019 年 7 月 28 日)可以解决此问题,但这并非 AMD 第一次犯这种性质的错误,因此,如果您作为 VFIO 用户考虑在将来更新 BIOS,请考虑这一点。
AMD GPU 无法正确重置,导致 “Error: internal error: Unknown PCI header type ‘127’” 错误(与上述问题不同)
直通 AMD GPU 可能会导致一个称为 “AMD 重置错误” 的问题。 在虚拟机电源循环后,GPU 无法正确重置其状态,这会导致设备发生故障,直到主机也重新启动。 这通常与 Windows 虚拟机中的 “code 43” 驱动程序错误以及主机上 libvirt 日志中的消息 “Error: internal error: Unknown PCI header type '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 的行为。 例如,一位拥有配备 Radon Pro WX 3200 AMD GPU 的 Dell Precision 7540 笔记本电脑的用户报告说,在 AMD GPU 绑定到 vfio-pci 的情况下,直通的 AMD GPU 被限制为 501 MHz,而不是正确的 1295 MHz 限制。 使用内核命令行禁用 amdgpu 内核模块是一种解决方法。
有关更多详细信息,请参阅此内核邮件列表讨论。
主机时钟源是 HPET 而不是 TSC
如果您的主机时钟源是 HPET,您可能会看到游戏性能严重下降。 虽然这个问题在虚拟机外部运行游戏时也会发生,但不知情的用户可能会开始对他们的虚拟机进行故障排除,而问题实际上出在他们的主机系统上。
要查看您当前的 clocksource,请在您的主机上运行此命令
$ cat /sys/devices/system/clocksource/clocksource*/current_clocksource
如果您的 clocksource 是 HPET,您可以尝试通过设置 内核参数 clocksource=tsc
和 tsc=reliable
强制将其更改为 TSC。 有关更多信息,请参阅这个 Reddit 帖子。
在 BIOS 中启用 Resizable BAR 时出现代码 43 错误
如果您拥有支持 Resizable BAR / SAM 的 GPU 并且启用了相应的 BIOS 选项,您可能会遇到代码 43 错误,因为此功能在 QEMU 中被禁用 [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
确认值。
操作系统挂起
如果您的虚拟化操作系统(尤其是 Windows)在连接 GPU 时挂起,您也可以尝试打开主板设置并
- 禁用
Re-Size BAR Support
, - 禁用
Above 4G memory / Crypto Currency mining
, - 启用
SR-IOV
, - 将
Max TOLUD
从dynamic
更改为特定值, - 将
Initiate Graphic Adapter
从PEG
切换到IGD
。
至少,VEGA 显卡似乎对这些设置很挑剔,除非您更改它们,否则实际上无法工作。