PCI passthrough via OVMF (简体中文)
开放虚拟机固件(Open Virtual Machine Firmware, OVMF)是一个为虚拟机启用 UEFI 支持的项目。Linux 内核 3.9 之后的版本 和近期版本的 QEMU ,支持将显卡直通给虚拟机,让虚拟机在执行图形密集型任务的时候有接近实体机的图形性能。
如果你可以为宿主机准备一张备用的显卡(无论是集成显卡还是独立显卡,还是旧的 OEM 卡都可以,品牌不需要一致),并且你的硬件支持 PCI 直通(参见 #硬件要求),那么任何操作系统的虚拟机都可以使用一张专用显卡并得到接近于原生的图形性能。请参阅这篇演讲(PDF)了解有关技术的更多信息。
硬件要求
VGA直通依赖许多功能。这些功能尚未成为所有硬件的标配,所以您的硬件可能并不支持。如果要使用VGA直通,您的硬件必须满足如下条件:
- 您的CPU必须支持硬件虚拟化(为了使用 kvm)和 IOMMU(为了使用 VGA 直通)。
- Intel CPU 支持列表(VT-x 和 VT-d)
- 推土机架构及以后的所有的AMD CPU(包括 Zen)都应支持。
- 您的主板必须支持 IOMMU
- 芯片组和BIOS必须都支持IOMMU。确定是否支持可能不是十分容易(比如,主板说明书一般不会提到这些),但是在 Xen wiki 和 Wikipedia:List of IOMMU-supporting hardware 上有一个相当全面的支持列表。
- 分配给客户机的 GPU 的 ROM 必须支持 UEFI
- 如果你可以在这个列表中找到适用您显卡的 ROM,就代表您的显卡可以支持 UEFI。所有2012年之后的显卡都应该支持 UEFI,因为微软将 UEFI 支持作为 Windows 8 兼容认证的必须条件。
您可能需要一台备用或者支持多个输入的显示器连接到显卡。如果没有连接显示器,那么直通的显卡不会有任何输出,并且使用 Spcie 或是 VNC 连接到虚拟机并不会让直通的显卡工作,换言之,没有任何使用 VGA 直通所带来的性能提升。您可能还需要准备一套备用的键鼠或是能通过 SSH 连接到宿主机的设备,如果虚拟机的配置出现了什么问题,您至少还可以控制宿主机。
设置IOMMU
- IOMMU 是 Intel VT-d 和 AMD-Vi 的通用名称。
- VT-d 指的是直接输入/输出虚拟化(Intel Virtualization Technology for Directed I/O),不应与VT-x(x86平台下的Intel虚拟化技术,Intel Virtualization Technology)混淆。VT-x 可以让一个硬件平台作为多个“虚拟”平台,而 VT-d 提高了虚拟化的安全性、可靠性和 I/O 性能。
打开IOMMU之后可以使用 PCI Passthrough 和对于故障和恶意行为的内存保护。参见: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” ,也有可能是更通用的名称,比如“虚拟化技术”之类。有可能您主板的手册并不会解释这些。
设置内核参数以启用 IOMMU,注意不同品牌的 CPU 所需的内核参数并不同。
- 对于 Intel CPU(VT-d),使用
intel_iommu=on
。 - 对于 AMD CPU(AMD-Vi),使用
amd_iommu=on
。
您同时需要设置iommu=pt
,这将防止Linux试图接触(touching)无法直通的设备。
在重启之后,检查 dmesg 以确认 IOMMU 已经被正确启用:
dmesg | grep -e DMAR -e 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
确保组有效
这个脚本将输出您的 PCI 设备是如何被分配到 IOMMU 组之中的。如果这个脚本没有输出任何东西,这代表您并没有启用 IOMMU 支持或者是您的硬件不支持 IOMMU。
#!/bin/bash shopt -s nullglob for d in /sys/kernel/iommu_groups/*/devices/*; do n=${d#*/iommu_groups/*}; n=${n%%/*} printf 'IOMMU Group %s ' "$n" lspci -nns "${d##*/}" done;
例子:
IOMMU Group 0 00:00.0 Host bridge [0600]: Intel Corporation 2nd Generation Core Processor Family DRAM Controller [8086:0104] (rev 09) IOMMU Group 1 00:16.0 Communication controller [0780]: Intel Corporation 6 Series/C200 Series Chipset Family MEI Controller #1 [8086:1c3a] (rev 04) IOMMU Group 2 00:19.0 Ethernet controller [0200]: Intel Corporation 82579LM Gigabit Network Connection [8086:1502] (rev 04) IOMMU Group 3 00:1a.0 USB controller [0c03]: Intel Corporation 6 Series/C200 Series Chipset Family USB Enhanced Host Controller #2 [8086:1c2d] (rev ...
一个 IOMMU 组是将物理设备直通给虚拟机的最小单位(这不意味着您必须直通整个 USB 控制器,单独的 USB 设备,如键盘鼠标,可以单独设置直通)。例如,在上面的例子中,在 06:00.0 上的显卡和在6:00.1上的音频控制器被分配到13组,它们必须一起直通给虚拟机。前置 USB 控制器被分在了单独的一组(第2组),并不和 USB 扩展控制器(第10组)和后置USB控制器(第4组)分在一组,这意味着它们都可以在不影响其它设备的情况下直通给虚拟机。
注意
如果客户机所用显卡插在 CPU 提供的 PCI-E 插槽中
并非所有的PCI-E插槽均相同。许多主板同时具有 CPU 提供和 PCH 提供的 PCI-E 插槽。根据 CPU 不同,可能您 CPU 提供的 PCI-E 插槽并不能正确隔离。在这种情况下,PCI-E 插槽本身和所连接设备将分在同一组。
IOMMU Group 1 00:01.0 PCI bridge: Intel Corporation Xeon E3-1200 v2/3rd Gen Core processor PCI Express Root Port (rev 09) IOMMU Group 1 01:00.0 VGA compatible controller: NVIDIA Corporation GM107 [GeForce GTX 750] (rev a2) IOMMU Group 1 01:00.1 Audio device: NVIDIA Corporation Device 0fbc (rev a1)
如果上一节的脚本输出类似这样,那么您的 PCI-E 插槽就属于上面所述的情况。根据您在 PCI-E 插槽上连接的设备和PCI-E插槽是由PCH还是CPU提供的,您可能发现在需要被直通的组中还有其它的设备,您必须将整个组中的设备全部直通给虚拟机。如果您愿意这样做的话,那么就可以继续。否则,您需要尝试将显卡插入其他的 PCI-E 插槽(如果有的话)并查看这些插槽是否能正确隔离。您也可以安装 ACS 补丁来绕过这个限制,不过这也有相应的缺点。要获得关于 ACS 补丁的更多信息,请参阅#分离IOMMU组(ACS补丁)
隔离GPU
为了将设备分配给虚拟机,此设备和同在一个IOMMU组的所有设备必须将驱动程序更换为 stub 或是 vfio ,以防止宿主机尝试与其交互。对于大多数设备,在虚拟机启动时就可以完成这项工作。
但是,由于它们的大小和复杂性,GPU 驱动程序并不倾向于支持动态的重新绑定。所以一般您不能将宿主机所使用的显卡直通给虚拟机,这会导致驱动程序之间的冲突。因此,通常情况下建议在启动虚拟机之前手动绑定这些占位 (placeholder) 驱动,防止其他驱动程序尝试认领 (claim) 设备。
以下部分详细地介绍了如何配置GPU,以便在启动过程中尽早绑定这些占位 (placeholder) 驱动程序,这会使设备处于非活动状态,直到虚拟机认领 (claim) 设备或是切换到其他驱动程序。这是首选方法,因为相比于系统完全启动之后再切换驱动程序,这种方法要简单许多。
从 Linux 4.1 开始,内核包括了 vfio-pci
。这是一个 VFIO 驱动程序,与 pci-stub
的作用相同。但它可以在一定程度上控制设备,例如在不使用设备的时候将它们切换到 D3 状态。
$ modinfo vfio-pci
filename: /lib/modules/4.4.5-1-ARCH/kernel/drivers/vfio/pci/vfio-pci.ko.gz description: VFIO PCI - User Level meta-driver author: Alex Williamson <alex.williamson@redhat.com> ...
vfio-pci
通常通过 ID 来定位 PCI 设备,这意味着您只需要指定设备的ID就可以完成绑定。对于下面的 IOMMU 组,您可能希望将vfio-pci
与 10de:13c2
和 10de:0fbb
绑定,这将作为本章节其余部分的示例值。
IOMMU Group 13 06:00.0 VGA compatible controller: NVIDIA Corporation GM204 [GeForce GTX 970] [10de:13c2] (rev a1) IOMMU Group 13 06:00.1 Audio device: NVIDIA Corporation GM204 High Definition Audio Controller [10de:0fbb] (rev a1)}}
您需要加载vfio-pci
,并将设备-供应商ID参数传递给内核。
/etc/modprobe.d/vfio.conf
options vfio-pci ids=10de:13c2,10de:0fbb
vfio-pci
,因为他需要连接到宿主机以保持正常运行。但是,该组中的任何其它设备都应该绑定到vfio-pci
。但是,这并不能保证vfio-pci
会在其它图形驱动之前加载。为了确保这一点,我们需要在initramfs中静态绑定它和它的依赖项。按照vfio_pci
,vfio
,vfio_iommu_type1
,vfio_virqfd
的顺序添加到mkinitcpio:
/etc/mkinitcpio.conf
MODULES=(... vfio_pci vfio vfio_iommu_type1 vfio_virqfd ...)
另外,确保 modconf hook 在mkinitcpio.conf
的 HOOKS 列表中:
/etc/mkinitcpio.conf
HOOKS=(... modconf ...)
由于新模块已添加到 initramfs 配置中,因此必须重新生成initramfs。如果您在/etc/modprobe.d/vfio.conf
修改了设备ID,您同样需要重新生成它。以便在早期启动过程得知需要隔离的设备。
重启并验证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 的输出之中。有时尽管设备没有出现在 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、libvirt、ovmf[损坏的链接:replaced by edk2-ovmf]和virt-manager之后,将您的 OVMF 固件映像和运行时变量模板添加到 libvirt 配置中,以便让 virt-install
或是virt-manager
可以找到它们。
/etc/libvirt/qemu.conf
nvram = [ "/usr/share/ovmf/x64/OVMF_CODE.fd:/usr/share/ovmf/x64/OVMF_VARS.fd" ]
您现在可以启用并启动libvirtd.service
和它的日志记录组件virtlogd.socket
。
安装客户机系统
使用 virt-manager
配置虚拟机的大部分过程都无需指导,只要按照屏幕上的提示即可。
如果使用virt-manager
,则必须将用户添加到 libvirt
组。
但是,您仍应该特别注意如下步骤:
- 在虚拟机创建向导要求您命名虚拟机时(点击“完成”前的最后一步),勾选“在安装前自定义配置”。
- 在“概况”屏幕,将“固件”选为"UEFI",如果您不能选择它,检查是否在
/etc/libvirt/qemu.conf
正确配置了固件的路径并且重新启动了libvirtd.service
。
- 在“CPUs”屏幕,将CPU型号改为"
host-passthrough
"。如果它不在列表中,则您必须手动输入。这确保您的CPU会被正确检测,从而能使 libvirt 完全暴露您 CPU 的功能,而不是仅仅是它所识别到的那些(这会让 CPU 按默认行为运作)。如果不进行这项设置,某些程序可能会抱怨说您的 CPU 型号是未知的。 - 如果要最小化IO开销,请点击“添加硬件”,并在“控制器:中选择“SCSI”类型,型号为 "VirtIO SCSI"。你可以将默认的 IDE 磁盘改为 SCSI 磁盘,并绑定到上述的控制器。
- Windows 不包含VirtIO驱动程序,所以你需要从这里下载包含驱动程序的 ISO 并且添加一个IDE CDROM(Windows 7之后可以使用SATA)并且连接到刚才的 ISO 。否则在安装过程中 Windows 无法识别 VirtIO 控制器。当 Windows 安装程序要求您选择要安装的磁盘时,加载 CD-ROM 下 visscsi 目录下的驱动程序。
其余的安装过程将使用在窗口中运行的标准QXL图形适配器进行。此时,无需为其余虚拟设备安装驱动程序,因为一些虚拟设备将被删除。客户机操作系统安装完成之后,只需要关闭虚拟机即可。您还可能会直接进入UEFI菜单,而不是自动从安装程序引导。客户机在启动的时候可能并未检测到正确的ISO文件,您需要手动指定引导顺序。点击“exit”并选择“boot manager”,您将会进入一个选择引导设备的菜单。
附加PCI设备
当安装完成之后,就可以编辑libvirt中的硬件详情并且删除一些虚拟设备。例如spcie通道和虚拟显示器,QXL图形适配器,虚拟鼠标键盘以及数位板设备。由于您没有可用的输入设备,您可能还想将几个主机上的USB设备附加到虚拟机。但请记住,至少为宿主机留下一个鼠标和/或键盘,防止客户机出现问题的时候无法操作宿主机。此时,可以添加先前隔离的PCI设备。只需单击“添加硬件”然后选择需要使用的PCI主机设备。如果一切顺利,连接到GPU的显示器上应该会显示OVMF启动画面,并可以正常启动。您可以在这时安装其他的驱动程序。
注意
OVMF虚拟机不能引导非EFI镜像
OVMF固件不支持启动非EFI介质。如果启动虚拟机之后您直接看到了UEFI Shell,可能是您的引导介质无效。尝试使用其他的引导映像来确定问题。
性能调整
使用PCI直通多数涉及性能密集型领域,例如游戏或是GPU加速任务。虽然PCI直通本身就是实现原生性能的一步,但宿主机和客户机仍可以通过一些调整来提升性能。
CPU核心固定
默认情况下,KVM将虚拟机的操作作为虚拟处理器的多个线程运行(The default behavior for KVM guests is to run operations coming from the guest as a number of threads representing virtual processors)。这些线程由Linux调度程序管理,如同其他线程一样。并根据 niceness 和 priority 分配给任何可用的CPU核心。因此,当线程切换到另一个核心时,核心的高速缓存将无法发挥作用,这可能会显著影响虚拟机的性能。CPU核心固定旨在解决这些问题,因为它会忽略Linux的线程调度并确保虚拟机的线程始终在特定的内核上运行。例如客户机的核心 0,1,2,3 分别映射到宿主机的 4,5,6,7 核心。
CPU 拓扑
大多数现代CPU都支持硬件多任务处理,即 Intel CPU 上的超线程或 AMD CPU 上的 SMT。 超线程/SMT 让一个物理核心具有两个虚拟线程。您需要根据虚拟机和宿主机的用途来设置 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
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
如上所示,对于上面的 AMD CPU,CPU 0 和 CPU 1 对应 CORE 0,对于上面的英特尔 CPU,CPU 0 和 CPU 6 对应 CORE 0。
如果您不需要虚拟机使用所有核心,那么最好给宿主机留下一个核心。选择要用于宿主机或虚拟机的核心应该基于CPU的特定硬件特性,但在大多数情况下,“CORE0”是宿主机的不错选择。如果为宿主机保留了任何核心,建议将模拟器和 iothreads(如果使用)固定到宿主机核心而不是虚拟 CPU 上。这可以提高性能并减少虚拟机的延迟,因为这些线程不会污染缓存或争抢虚拟 CPU 线程调度。如果虚拟机需要使用所有核心,那么固定模拟器或 iothreads 是没有必要的。
XML 示例
4c/1t 无超线程的例子
$ 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> ...
6c/2t Intel CPU 固定的例子
$ 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> ... <topology sockets='1' cores='4' threads='2'/> ...
4c/2t AMD CPU 的例子
$ 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> ... <topology sockets='1' cores='4' threads='2'/> ...
如果您不打算在使用虚拟机时在宿主机上进行计算繁重的工作(或是根本不打算在宿主机上做任何事情),您可以将虚拟线程固定在所有核心上以便充分使用 CPU 时间。不过,固定所有物理和虚拟核心可能会导致虚拟机出现延迟。
内存大分页
在处理需要大量内存的应用程序时,内存延迟可能会是一个问题,因为使用的内存分页越多,程序尝试跨分页访问信息的可能性就越高(页是基本的内存分配单位)。将分页解析为实际的内存地址需要多个步骤,因此CPU通常将信息缓存在最近使用的分页上,以加快后续的内存使用。使用大量内存的应用程序可能会遇到内存性能问题:例如,虚拟机使用 4GB 内存,分页大小为 4KB(这是普通分页的默认大小),总共104万页,这意味着缓存命中率可能会大幅降低并大幅增加内存延迟。大分页通过向应用程序提供更大的单个分页来缓解这个问题,从而增加了多个操作中连续位于同一分页的几率。
透明大分页
QEMU 将在 QEMU 或 Libvirt 中自动使用 2 MiB 大小的透明大分页,但这可能并不那么顺利,在使用 VFIO 时,页面会在启动时锁定,并且在虚拟机首次启动时会预先分配透明的大分页。如果内存高度碎片化,或者虚拟机正在使用大部分剩余内存,则内核可能没有足够的2 MiB 页面来完全满足分配。在这种情况下,将会混合使用使用 2 MiB 和 4 KiB 分页,从而无法得到足够的性能提升。由于分页在 VFIO 模式下被锁定,因此内核无法在虚拟机启动后将这些 4 KiB 分页转换为大分页。THP(透明大分页)可用的 2 MiB 大页面数量与通过以下各节中描述的#内存大分页机制相同。
要查看全局使用的 THP 内存量:
$ grep AnonHugePages /proc/meminfo AnonHugePages: 8091648 kB
要查看特定 QEMU 实例所使用的 THP ,需要指定 QEMU 的 PID:
$ grep -P 'AnonHugePages:\s+(?!0)\d+' /proc/[PID]/smaps AnonHugePages: 8087552 kB
在这个例子中,虚拟机分配了 8388608 KiB 的内存,但只有 8087552 KiB 可通过 THP 获得。 剩下的301056KiB被分配为 4 KiB 分页。没有任何警告提示使用了 4 KiB 分页。因此,THP 的有效性在很大程度上取决于虚拟机启动时宿主机系统的内存碎片。如果这是不可接受的,建议使用#内存大分页。
Arch 内核已经编译并默认启用了 THP,默认为 madvise
模式。可在 /sys/kernel/mm/transparent_hugepage/enabled
查看。
Static huge pages
While transparent huge pages should work in the vast majority of cases, they can also be allocated statically during boot. This should only be needed to make use 1 GiB hugepages on machines that support it, since transparent huge pages normally only go up to 2 MiB.
To allocate huge pages at boot, one must simply specify the desired amount on their kernel command line with hugepages=x
. For instance, reserving 1024 pages with hugepages=1024
and the default size of 2048 KiB per huge page creates 2 GiB worth of memory for the virtual machine to use.
If supported by CPU page size could be set manually. 1 GiB huge page support could be verified by grep pdpe1gb /proc/cpuinfo
. Setting 1 GiB huge page size via kernel parameters : default_hugepagesz=1G hugepagesz=1G hugepages=X
.
Also, since static huge pages can only be used by applications that specifically request it, you must add this section in your libvirt domain configuration to allow kvm to benefit from them :
$ virsh edit [vmname]
... <memoryBacking> <hugepages/> </memoryBacking> ...
Dynamic huge pages
Hugepages could be allocated manually via vm.nr_overcommit_hugepages
sysctl parameter.
/etc/sysctl.d/10-kvm.conf
vm.nr_hugepages = 0 vm.nr_overcommit_hugepages = num
Where num
- is the number of huge pages, which default size if 2 MiB.
Pages will be automatically allocated, and freed after VM stops.
More manual way:
# echo num > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages # echo num > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
For 2 MiB and 1 GiB page size respectively. And they should be manually freed in the same way.
It is hardly recommended to drop caches, compact memory and wait couple of seconds before starting VM, as there could be not enough free contiguous memory for required huge pages blocks. Especially after some uptime of the host system.
# echo 3 > /proc/sys/vm/drop_caches # echo 1 > /proc/sys/vm/compact_memory
Theoretically, 1 GiB pages works as 2 MiB. But practically - no guaranteed way was found to get contiguous 1 GiB memory blocks. Each consequent request of 1 GiB blocks lead to lesser and lesser dynamically allocated count.
CPU调速器
根据您CPU 调速器的配置方式,虚拟机的线程可能无法达到负载阈值以提升频率。实际上,KVM 自身无法更改 CPU 频率,如果 CPU 频率并不能随着虚拟 CPU 的负载使用而提升,这可能是一个严重的性能问题。检查此问题最简单的方法是在虚拟机上运行 CPU 密集型任务的同时在宿主机上运行watch lscpu
检查CPU频率是否上升。如果您的 CPU 频率无法正常上升且无法达到所报告的最大值,可能是由于主机操作系统限制了 CPU 升频。这种情况下,尝试手动将所有CPU内核设为最大频率查看是否能够提升性能。请注意,如果您使用带有默认pstate驱动程序的现代Intel芯片,则cpupower命令无效,因此请监视/proc/cpuinfo
确保您的CPU处于最大频率。
高DPC延迟
如果您在虚拟中遇到了高DPC和/或中断延迟,请确保已在主机内核上加载了所需的VirtIO内核模块,可加载的VirtIO内核模块包括:virtio-pci
,virtio-net
,virtio-blk
,virtio-balloon
,virtio-ring
和virtio
。
加载一个或多个这些模块后,在宿主机上执行lsmod | grep virtio
不应返回空。
使用isolcpus固定CPU核心
此外,确保您已正确隔离CPU。 在本例中,我们假设您使用的是CPU 4-7。
使用内核参数isolcpus nohz_full rcu_nocbs
将CPU与内核完全隔离。
sudo vim /etc/defaults/grub
... GRUB_CMDLINE_LINUX="..your other params.. isolcpus=4-7 nohz_full=4-7 rcu_nocbs=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上的这篇帖子了解详情。
提高AMD CPU的性能
以前,由于一个非常老的bug,必须在运行 KVM 的 AMD 系统上禁用嵌套页表(NPT)以提高 GPU 性能,但这将降低 CPU 性能,导致卡顿。
有一个内核补丁可以解决这个问题,它已经在4.14-stable和4.9-stable 版本被接受了。 如果你正在运行官方的linux或linux-lts内核,那么该补丁被已经应用了(请确保你使用的是最新内核)。如果您正在运行另一个内核,您可能需要手动应用补丁。
进一步调整
Red Hat的虚拟化调试和优化指南(中文)中提供了更专业的VM调优技巧。 如果您遇到以下情况,本指南可能特别有用:
- 在从虚拟中下载/上传期间,主机CPU负载较高,请参阅“桥接零复制传输”了解可能的修复。
- 尽管使用了virtio-net,但是在下载/上传期间,访客网络速度有限制。请参阅“多队列virtio-net”以获得可能的修复。
- 在没有达到负载极限的情况下,虚拟机在高I/O下卡顿。请参阅“多队列virtio-scsi”以获得获得可能的修复。
特殊步骤
某些机器需要特定的配置调整才能正常工作。 如果您在的宿主机或虚拟机不能正常工作,请查看您的系统是否符合以下情况之一,并尝试相应地调整配置。
宿主机和客户机使用相同的GPU
由于vfio-pci
如何使用您的供应商和设备ID对来识别需要在启动时绑定哪个设备,如果您的两个GPU 具有相同的ID,您将无法让您的直通驱动程序只与一个绑定。这种情况下必须使用脚本完成配置,无论您使用哪个驱动程序,都需要使用driver_override
机制由pci总线地址完成分配。
脚本示例
直通除了启动时所用GPU之外的所有GPU
在/usr/bin/vfio-pci-override.sh
,创建一个脚本让vfio-pci
绑定除了启动时所用GPU之外的所有GPU。
#!/bin/sh for i in /sys/devices/pci*/*/boot_vga; do if [ $(cat "$i") -eq 0 ]; then GPU="${i%/boot_vga}" AUDIO="$(echo "$GPU" | sed -e "s/0$/1/")" echo "vfio-pci" > "$GPU/driver_override" if [ -d "$AUDIO" ]; then echo "vfio-pci" > "$AUDIO/driver_override" fi fi done modprobe -i vfio-pci
直通选定的GPU
手动指定需要绑定的GPU。
#!/bin/sh GROUP="0000:00:03.0" 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/$GROUP/$DEV/driver_override done fi modprobe -i vfio-pci
脚本安装
创建/etc/modprobe.d/vfio.conf
,内容如下:
install vfio-pci /usr/bin/vfio-pci-override.sh
编辑/etc/mkinitcpio.conf
:
从MODULES
中移除所有图形驱动程序,并添加vfio-pci
和vfio_iommu_type1
MODULES=(ext4 vfat vfio-pci vfio_iommu_type1)
将/etc/modprobe.d/vfio.conf
和/usr/bin/vfio-pci-override.sh
添加到FILES
FILES=(/etc/modprobe.d/vfio.conf /usr/bin/vfio-pci-override.sh)
重新生成initramfs并重新启动。
直通启动时所用的GPU
标记为boot_vga
的GPU在进行PCI直通时比较特殊,BIOS需要使用它才能显示启动消息或BIOS配置菜单等内容。为了完成达到这个目的,需要创建一个可以被自由修改的VGA boot 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替换为您自己所需要的值。计算方法如下:
宽 x 高 x 4 x 2 = 总比特数 总比特数 / 1024 / 1024 = 兆比特数 + 2
例如,分辨率为1920x1080
1920 x 1080 x 4 x 2 = 16,588,800 比特 16,588,800 / 1024 / 1024 = 15.82 MB + 2 = 17.82
实际上的数字应该是大于计算的得数的一个2的幂,因为17.82大于16,所以应该选择32。
接下来使用一个脚本创建共享内存文件。
/usr/local/bin/looking-glass-init.sh
#!/bin/sh touch /dev/shm/looking-glass chown user:kvm /dev/shm/looking-glass chmod 660 /dev/shm/looking-glass
将user
改为你的用户名。
记得为脚本授予可执行权限。
创建一个systemd单元文件,以便在开机时创建这个文件。
/etc/systemd/system/looking-glass-init.service
[Unit] Description=Create shared memory for looking glass [Service] Type=oneshot ExecStart=/usr/local/bin/looking-glass-init.sh [Install] WantedBy=multi-user.target
启动并启用 looking-glass-init.service
。
将IVSHMEM主机安装到Windows客户机
目前Windows不会通知用户发现新的IVSHMEM设备,它会默默安装虚拟驱动程序。要实际启用该设备,您必须进入设备管理器并在“系统设备”下为“PCI标准RAM控制器”更新设备的驱动程序。因为它在其他地方尚未提供,所以必须从issue 217下载签名的驱动程序。
安装驱动程序后,您必须下载最新的looking-glass-host,然后在您的客户机上启动它。为了运行它,您还需要安装Microsoft Visual C ++ Redistributable。可以通过编辑HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
注册表并添加路径来自动启动它。
安装客户端
Looking glass 客户端可以通过 looking-glassAUR或looking-glass-gitAUR 安装。
当客户机配置完成之后,您可以通过如下方式运行运行客户端:
$ looking-glass-client
如果您不想通过spcie传递键盘和鼠标的话,你可以禁用它:
$ looking-glass-client -s
您也可能希望全屏运行客户端,因为图像缩放会导致质量下降:
$ looking-glass-client -F
使用--help
选项查看更多信息。
热切换外围设备
Looking Glass 包含一个spcie客户端,以便控制Windows客户机。然而,对于某些应用程序,例如游戏,可能延迟太高了。另一种方法是直通特定的USB设备以尽可能地降低延迟。可以在主机和客户机之间热切换设备。
首先为您想要直通的设备创建一个.xml
文件,libvirt使用该文件来识别设备。
/home/$USER/.VFIOinput/input_1.xml
<hostdev mode='subsystem' type='usb' managed='no'> <source> <vendor id='0x[Before Colon]'/> <product id='0x[After Colon]'/> </source> </hostdev>
将[before / After Colon]
替换为lsusb
命令的内容,取决于于您要传递的设备。
例如,我的鼠标是Bus 005 Device 002: ID 1532:0037 Razer USA, Ltd
,所以我的vendor id
是 1532,product id
是 1037。
重复以上步骤以添加所有想要直通的USB设备。如果您的键盘/鼠标在lsusb
中有多个条目,那么请为每个条目创建.xml文件。
接下来需要创建一个bash脚本来告诉libvirt附加/分离设备。
/home/$USER/.VFIOinput/input_attach.sh
#!/bin/bash virsh attach-device [VM-Name] [USBdevice]
将[VM-Name]
替换为您的虚拟机名称,名称可以在virt-manager下看到。另外,将[USBdevice]
替换为您希望传递的设备的.xml文件的完整路径。为多个设备添加额外的行。例如,这是我的脚本:
/home/ajmar/.VFIOinput/input_attach.sh
#!/bin/bash virsh attach-device win10 /home/ajmar/.VFIOinput/input_mouse.xml virsh attach-device win10 /home/ajmar/.VFIOinput/input_keyboard.xml
接下来复制脚本文件并用detach-device
替换attach-device
。并为每个脚本设置可执行权限。
现在可以执行这2个脚本,以将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
用宿主机IP 地址替换$HOST_IP
,用root密码替换$ROOTPASSWORD
。
您可能还希望使用快捷键来执行脚本文件。在Windows上,您可以使用Autohotkey,在宿主机上可以使用Xbindkeys,您的桌面环境也可能提供快捷键设置。 由于需要以root身份运行脚本,您可能还需要使用Polkit,从而无需输入密码。
- (译注)如果您配置了Polkit无密码授权,那么这个脚本也可以使用您自己的用户来执行。参见Libvirt (简体中文)#设置授权和Polkit (简体中文)#跳过口令提示.
- (译注)您也可以使用Windows 10 自带的 OpenSSH,使用方法和Linux下相同,参见OpenSSH。
分离IOMMU组(ACS补丁)
如果您发现您的PCI设备与其他不希望直通的设备分在一组,您可以使用Alex Williamson的ACS补丁将它们分离。使用前请确保了解其中的潜在风险。
您需要一个应用了这个补丁的内核,最简单的办法就是安装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
通常就足够了。
安装和配置后,设置内核参数以在启用pcie_acs_override=
选项的情况下加载新内核。
仅使用QEMU不使用libvirt
如果不想在libvirt的帮助下设置虚拟机,可以使用具有自定义参数的普通QEMU命令来运行要使用PCI直通的虚拟机,这对于脚本等一些用例来说是十分理想的,可以灵活地搭配其他脚本。
先完成#设置IOMMU和#隔离GPU,接着配置好QEMU虚拟化环境,启用KVM并使用-device vfio-pci,host=07:00.0
选项,将07:00.0
替换为您先前隔离的设备ID。
要使用OVMF固件,确保已安装ovmf[损坏的链接:replaced by edk2-ovmf],将UEFI固件从/usr/share/ovmf/x64/OVMF_VARS.fd
复制到临时位置,如/tmp/MY_VARS.fd
。最后指定OVMF路径。使用如下的参数(注意顺序):
-
-drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF_CODE.fd
指向实际的位置,注意这是只读的 -
-drive if=pflash,format=raw,file=/tmp/MY_VARS.fd
指向临时位置
建议通过使用virtio驱动程序并进一步配置,以增强性能,参阅QEMU。
您还可能必须使用-cpu host,kvm=off
参数将主机的CPU型号信息转发给虚拟机,并欺骗Nvidia和其他制造商的设备驱动程序使用的虚拟化检测,防止驱动程序拒绝在虚拟机中运行。
直通其它设备
USB控制器
如果你的主板具有多个USB控制器并映射到不同的组,则可以直通这些控制器而不是单个USB设备。直通整个控制器有如下的优势:
- 如果设备在某些操作(例如正在进行更新的手机)的过程中断开或更改ID,虚拟机不会突然丢失设备。
- 由该控制器管理的任何USB端口都由虚拟机直接处理,并且可以将其设备拔出,重新插入和更改,而无需通知管理程序。
- 如果启动VM时通常直通给虚拟机的其中一个USB设备丢失,Libvirt将不会抱怨。
与GPU不同,大多数USB控制器的驱动程序不需要任何特定配置即可在VM上运行,并且通常可以在主机和客户机系统之间来回传递控制而不会产生任何副作用。
您可以使用以下命令找出哪个PCI设备对应于哪个控制器以及每个端口和设备对应哪个控制器:
$ for usb_ctrl in $(find /sys/bus/usb/devices/usb* -maxdepth 0 -type l); do pci_path="$(dirname "$(realpath "${usb_ctrl}")")"; echo "Bus $(cat "${usb_ctrl}/busnum") --> $(basename $pci_path) (IOMMU group $(basename $(realpath $pci_path/iommu_group)))"; lsusb -s "$(cat "${usb_ctrl}/busnum"):"; 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主机设备列表中即可。不需要其他配置。
使用PulseAudio将虚拟机音频传递给宿主机
可以使用libvirt将虚拟机的音频作为应用程序传输到宿主机。 这具有以下优点:多个音频流可传输到一个主机输出,并且与无需考虑音频设备是否支持直通。PulseAudio 是必需的。
PulseAudio守护进程通常在你的普通用户下运行,并且只接受来自相同用户的连接。然而libvirt默认使用root运行QEMU。为了让QEMU在普通用户下运行,编辑/etc/libvirt/qemu.conf
并将user
设置为你的用户名。
user = "dave"
你同样需要告诉QEMU使用PulseAudio后端并识别要连接的服务器,首先将QEMU命名空间添加到你的域。
编辑域的.xml
文件(使用virsh edit domain
),将domain type
行改为:
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'[失效链接 2020-08-04 ⓘ]>
并且加入如下的配置(在最后一个</devices>
之后,</domain>
之前):
<qemu:commandline> <qemu:env name='QEMU_AUDIO_DRV' value='pa'/> <qemu:env name='QEMU_PA_SERVER' value='/run/user/1000/pulse/native'/> </qemu:commandline>
1000
是你的用户ID,如果必要的话改为你的用户ID,用户ID可以通过id
命令查看。
请记住在继续之前保存文件并退出编辑器,否则更改将不会保存。如果您看到:“已更改域 [名称] XML配置”或是类似的消息,这表示您的更改已保存。
重启 libvirtd.service
和 用户服务 pulseaudio.service
。
现在,虚拟机音频将作为应用程序传递给宿主机。pavucontrol可用于控制输出设备。请注意,在Windows客户机上,不使用消息信号中断可能会导致出现噼啪声。
物理磁盘/分区
可以通过向XML添加条目来使用整个磁盘或分区来提高I/O性能。
$ virsh edit [vmname]
<devices> ... <disk type='block' device='disk'> <driver name='qemu' type='raw' cache='none' io='native'/> <source dev='/dev/sdXX'/> <target dev='vda' bus='virtio'/> <address type='pci' domain='0x0000' bus='0x02' slot='0x0a' function='0x0'/> </disk> ... </devices>
在Windows客户机上需要安装驱动程序才能工作,请参阅#安装客户机系统。
注意
直通不支持Reset的设备
当虚拟机关闭时,虚拟机使用的所有设备都会被其操作系统取消初始化,以准备关闭。在这种状态下,这些设备不再能使用,必须先重新上电才能恢复正常运行。Linux可以自己处理这种电源循环,但是当设备没有已知的Reset方法时,它将于禁用状态并不再可用。Libvirt和Qemu都希望所有宿主机的PCI设备在完全停止虚拟机之前准备好重新连接到宿主机,当遇到不会重置的设备时,虚拟机将挂起“关闭”状态,并将无法重新启动,除非宿主机系统重新启动。 因此,推荐仅直通支持重置的PCI设备,这可以通过PCI设备sysfs节点中存在reset
文件来确定,例如/sys/bus/pci/devices/0000:00:1a.0/reset
。
以下bash命令显示设备是否可以被Reset。
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控制器无法Reset,将导致虚拟机无法正常关闭,而在00:1b.0
中的集成声卡和在00:1a.0
和00:1d.0
中的其他两个控制器没有这个问题,可以毫无问题地直通。
完整的例子
如果您在配置的时候遇到了麻烦,您可以参考完整的配置示例。一些用户在那里描述了它们的配置步骤,或许您可以从中发现一些技巧。
疑难解答
如果您遇到的问题并没有在下面列出,您也可以参考QEMU#Troubleshooting。
Nvidia GPU直通到Windows 虚拟机时发生"错误43:驱动程序加载失败”
- 这也可能修复与Nvidia驱动程序相关的SYSTEM_THREAD_EXCEPTION_NOT_HANDLED引导失败。
- 这可能也会修复Linux虚拟机的问题。
从337.88开始,Windows上的Nvidia驱动程序将会检查虚拟机管理程序是否正在运行,如果检测到虚拟机管理程序,则会拒绝加载,这会导致Windows设备管理器出现错误43。从QEMU 2.5.0和libvirt 1.3.3开始,可以设置虚假的vendor_id,这足以欺骗Nvidia驱动程序。所需的步骤是将hv_vendor_id=随便什么的
添加到QEMU命令行中的cpu参数,或者将以下行添加到libvirt的域配置中。 ID必须设置为12个字符的字母数字(例如'1234567890ab')。
$ virsh edit [vmname]
... <features> <hyperv> ... <vendor_id state='on' value='whatever'/> ... </hyperv> ... <kvm> <hidden state='on'/> </kvm> </features> ...
使用旧版QEMU和/或libvirt的用户将必须禁用一些虚拟机管理程序扩展,这会大大降低性能。如果必须这样做的话,请在libvirt域配置文件中执行以下替换。
$ virsh edit [vmname]
... <features> <hyperv> <relaxed state='on'/> <vapic state='on'/> <spinlocks state='on' retries='8191'/> </hyperv> ... </features> ... <clock offset='localtime'> <timer name='hypervclock' present='yes'/> </clock> ...
... <clock offset='localtime'> <timer name='hypervclock' present='no'/> </clock> ... <features> <kvm> <hidden state='on'/> </kvm> ... <hyperv> <relaxed state='off'/> <vapic state='off'/> <spinlocks state='off'/> </hyperv> ... </features> ...
在启动虚拟机后在dmesg中看到"BAR 3: cannot reserve [mem]"错误
查看这篇文章:
如果你仍然遇到错误43,请在启动虚拟机后检查dmesg是否存在内存保留错误,如果您有类似消息,则可能是这种情况:
vfio-pci 0000:09:00.0: BAR 3: cannot reserve [mem 0xf0000000-0xf1ffffff 64bit pref]
找出您的GPU所连接的PCI Bridge。这条命令将给出设备的实际层次结构:
$ lspci -t
在启动VM之前运行以下命令,将ID替换为来自先前输出的实际ID。
# echo 1 > /sys/bus/pci/devices/0000\:00\:03.1/remove # echo 1 > /sys/bus/pci/rescan
VBIOS的UEFI (OVMF) 兼容
With respect to this article:
Error 43 can be caused by the GPU's VBIOS without UEFI support. To check whenever your VBIOS supports it, you will have to use rom-parser
:
$ git clone https://github.com/awilliam/rom-parser $ cd rom-parser && make
Dump the 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
And test it for compatibility:
$ ./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
To be UEFI compatible, you need a "type 3 (EFI)" in the result. If it's not there, try updating your GPU VBIOS. GPU manufacturers often share VBIOS upgrades on their support pages. A large database of known compatible and working VBIOSes (along with their UEFI compatibility status!) is available on TechPowerUp.
Updated VBIOS can be used in the VM without flashing. To load it in QEMU:
-device vfio-pci,host=07:00.0,......,romfile=/path/to/your/gpu/bios.bin \
And in libvirt:
<hostdev> ... <rom file='/path/to/your/gpu/bios.bin'/> ... </hostdev>
One should compare VBIOS versions between host and guest systems using nvflash (Linux versions under Show more versions) or GPU-Z (in Windows guest). To check the currently loaded 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 ...
And to check a given VBIOS file:
$ ./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 ...
If the external ROM did not work as it should in the guest, you will have to flash the newer VBIOS image to the GPU. In some cases it is possible to create your own VBIOS image with UEFI support using GOPUpd tool, however this is risky and may result in GPU brick.
In order to avoid the irreparable damage to your graphics adapter it is necessary to unload the NVIDIA kernel driver first:
# modprobe -r nvidia_modeset nvidia
Flashing the VBIOS can be done with:
# ./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。
启用它的过程有些复杂,可以在此处找到说明。
其他的提示可以从lime-technology's wiki上找到, 或参考这篇帖子VFIO tips and tricks。
网上有一些一些工具,例如 MSI_util
,不过它们在 Windows 10 64bit下无效。
要解决这个问题,仅仅在function 0(01:00.0 VGA compatible controller: NVIDIA Corporation GM206 [GeForce GTX 960] (rev a1) (prog-if 00 [VGA controller])
)上启用MSI是不够的,另一个function(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
)可能会恢复音频,有关设置igfx_off
的详细信息,请阅读intel-iommu.html。
启用vfio_pci后X无法启动
这与主机GPU被检测为额外GPU有关,当它尝试加载虚拟机所用的GPU的驱动程序时,会导致X崩溃。为了避免这种情况,需要一个指定主机GPU的BusID的Xorg配置文件。 可以从lspci或Xorg日志获取正确的BusID。来源 →
/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设备来选择GPU,而不是系统中可用的OpenGL渲染器 - 结果是Chromium可能会忽略可用于渲染的集成GPU并尝试使用绑定到无法在宿主机上使用的虚拟机专用GPU。这导致使用软件渲染(导致更高的CPU负载,也可能导致视频播放不稳定,滚动不平滑)。
这可以通过告诉Chromium使用哪个GPU来解决。
虚拟机只使用一个核心
对于某些用户,即使启用了IOMMU并且核心计数设置为大于1,VM仍然只使用一个CPU核心和线程。 要解决此问题,请在virt-manager
中启用“手动设置CPU拓扑”,并将其设置为所需的CPU,内核和线程数量。 请记住,“线程”是指每个CPU的线程数,而不是总数。
直通貌似工作但没有输出
确保您为您的虚拟机选择了UEFI固件。此外,请确保已将正确的设备直通给虚拟机。
virt-manager 遇到权限问题
如果你在virt-manager中遇到权限问题,将以下内容添加到/etc/libvirt/qemu.conf
:
group="kvm" user="user"
如果仍然不能工作,确保你的用户已经加入了kvm和libvirt组。
虚拟机关闭之后宿主机核心无响应
此问题似乎主要影响运行Windows 10 guest虚拟机的用户,并且通常在虚拟机运行很长一段时间后发生:主机的多个CPU核心被锁定(请参阅[3])。要解决此问题,请尝试在直通的GPU上启用消息信号中断。有关如何执行此操作的指南,请参见[4]。
客户机在宿主机休眠眠情况下运行导致死机
启用VFIO的虚拟机如果在睡眠/唤醒周期中运行时会变得不稳定,并且在尝试关闭它们时会导致主机死机。为了避免这种情况,可以使用以下libvirt hook脚本和systemd单元在虚拟机运行时阻止主机进入休眠状态。hook文件需要可执行权限才能工作。
/etc/libvirt/hooks/qemu
#!/bin/bash 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
AMD显卡直通后无法重启虚拟机
某些AMD显卡在Windows虚拟机中可能无法正常Reset,导致虚拟机无法重新启动。
您可以通过在关闭虚拟机之前手动安全移除显卡(就像移除U盘那样)来避免这个问题。当您安装VirtIO驱动程序映像中的guest-agent
之后,您就可以在安全移除菜单中看到相关的设备。
如果您希望自动完成这项任务,请参阅[5]。