PCI passthrough via OVMF

出自 Arch Linux 中文维基

開放虛擬機固件(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)都應支持。
      • K10架構(2007)的 CPU 不具備 IOMMU,你需要一張890FX或者990FX芯片組的主板來使用 IOMMU(這些主板有自己的IOMMU支持)。
  • 您的主板必須支持 IOMMU
  • 分配給客戶機的 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。因為內核參數CONFIG_INTEL_IOMMU_DEFAULT_ON在linux中並未啟用。
  • 對於 AMD CPU(AMD-Vi),當內核檢測到硬件支持IOMMU時就會自動啟用。

您同時需要設置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 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 組是將物理設備直通給虛擬機的最小單位(這不意味着您必須直通整個 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) 設備或是切換到其他驅動程序。這是首選方法,因為相比於系統完全啟動之後再切換驅動程序,這種方法要簡單許多。

警告: 在配置完成並重新啟動之後,無論您配置的 GPU 是什麼,它都將無法在宿主機上使用,直到您取消配置。在執行操作之前,確保您要在宿主機上使用的GPU 已經被正確配置,並且您也應該在BIOS中將宿主機所使用的GPU設為主要GPU。

從 Linux 4.1 開始,內核包括了 vfio-pci 。這是一個 VFIO 驅動程序,與 pci-stub 的作用相同。但它可以在一定程度上控制設備,例如在不使用設備的時候將它們切換到 D3 狀態。

通過設備ID綁定vfio-pci[編輯 | 編輯原始碼]

vfio-pci通常通過 ID 來定位 PCI 設備,這意味着您只需要指定設備的ID就可以完成綁定。對於下面的 IOMMU 組,您可能希望將vfio-pci10de:13c210de: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)}}

注意:
  • 如果宿主機和客戶機所使用的GPU設備ID相同(例如是同一型號)那麼就不能使用供應商-設備ID來指定需要隔離的設備。如果是這種情況,參見 #宿主機和客戶機使用相同的GPU
  • 音頻設備(例如上面的10de:0fbb)不是必須綁定的,Libvrt能夠自動將其從snd_hda_intel解綁

有兩種方法可以實現設備ID與vfio-pci綁定。

方法一:在內核啟動參數中進行綁定。

通過內核啟動參數進行綁定,能夠方便地編輯、刪除、撤銷這類具有破壞性的配置。參考內核參數加入以下命令:

vfio-pci.ids=10de:13c2,10de:0fbb
注意:
  • 如果你使用nvidia驅動,並且對其進行了modesetting,則vfio-pci.ids必須通第二種方式進行綁定。第一種方法會導致參數生效過晚從而綁定失敗。

方法二:通過載入模塊進行綁定。

您需要在/etc/modprobe.d/創建vfio.conf,並將編輯內容如下:

/etc/modprobe.d/vfio.conf
options vfio-pci ids=10de:13c2,10de:0fbb

更改過後需要重新生成initramfs

注意:

提前加載vfio-pci[編輯 | 編輯原始碼]

mkinitcpio[編輯 | 編輯原始碼]

因為Arch的linux內核將vfio-pci作為一個模塊,我們需要強迫vfio-pci在顯卡驅動搶占硬件之前加載。為了達成這一目的,我們向mkinitcpio中添加 vfio_pci, vfio, vfio_iommu_type1, vfio_virqfd

注意:
  • 從內核 6.2 版本開始,vfio_virqfd 的功能已經併入 vfio 模塊
/etc/mkinitcpio.conf
MODULES=(... vfio_pci vfio vfio_iommu_type1 vfio_virqfd ...)
注意:
  • 如果你在其中加載了其他驅動來進行modesetting(例如nouveau, radeon, amdgpu, i915等)則上述vfio模組必須在他們之前加載

另外,確保 modconf hook 在mkinitcpio.conf的 HOOKS 列表中:

/etc/mkinitcpio.conf
HOOKS=(... modconf ...)

由於新模塊已添加到 initramfs 配置中,因此必須重新生成initramfs。若在/etc/modprobe.d/vfio.conf修改了設備ID,您同樣需要重新生成它。之後重啟系統。

booster[編輯 | 編輯原始碼]

與mkinitcpio類似,你需要自定義模組來提前加載:

/etc/booster.yaml
modules_force_load: vfio_pci,vfio,vfio_iommu_type1,vfio_virqfd

之後 重新生成initramfs

dracut[編輯 | 編輯原始碼]

與上面的思路類似, 在 /etc/dracut.conf.d添加以下文件:

10-vfio.conf
force_drivers+=" vfio_pci vfio vfio_iommu_type1 vfio_virqfd "

注意到我們使用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 的輸出之中。有時儘管設備沒有出現在 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[損壞的鏈接:package not found]libvirtovmf[損壞的鏈接: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核心固定的用戶可能會遇到卡頓和短暫掛起的問題。尤其是在使用MuQSS調度程序的情況下(存在與linux-ck內核和linux-zen內核)。如果遇到類似的問題,您可能需要首先禁用固定,保證始終具有最大的即時響應性能。

CPU 拓撲[編輯 | 編輯原始碼]

大多數現代CPU都支持硬件多任務處理,即 Intel CPU 上的超線程或 AMD CPU 上的 SMT。 超線程/SMT 讓一個物理核心具有兩個虛擬線程。您需要根據虛擬機和宿主機的用途來設置 CPU 核心固定。

要查看您的 CPU 拓撲,運行 lscpu -e

注意: 需要特別注意 "CORE" 欄,它表明了虛擬核心和物理核心的對應關係。

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 示例[編輯 | 編輯原始碼]

注意: 如果你的磁盤控制器沒有啟用 iothread,那就不要使用下面例子中的 iothreadiothread只在virtio-scsi 或是 virtio-blk 設備上工作。
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 隔離,考慮在未使用的物理/虛擬核心上使用 isolcpus 內核選項。

如果您不打算在使用虛擬機時在宿主機上進行計算繁重的工作(或是根本不打算在宿主機上做任何事情),您可以將虛擬線程固定在所有核心上以便充分使用 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.

警告: Static huge pages lock down the allocated amount of memory, making it unavailable for applications that are not configured to use them. Allocating 4 GiBs worth of huge pages on a machine with 8 GiB of memory will only leave you with 4 GiB of available memory on the host even when the VM is not running.

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[編輯 | 編輯原始碼]

本文或本章節的事實準確性存在爭議。

原因: Need futher testing if this variant as effective as static one(在 Talk:PCI passthrough via OVMF 中討論)


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延遲[編輯 | 編輯原始碼]

本文或本章節的事實準確性存在爭議。

原因: As far as I can tell all virtio modules listed here are for virtual devices used when Linux runs as a guest. Loading them on the host serves no purpose.(在 Talk:PCI passthrough via OVMF 中討論)


如果您在虛擬中遇到了高DPC和/或中斷延遲,請確保已在主機內核上加載了所需的VirtIO內核模塊,可加載的VirtIO內核模塊包括:virtio-pcivirtio-netvirtio-blkvirtio-balloonvirtio-ringvirtio

加載一個或多個這些模塊後,在宿主機上執行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 版本被接受了。 如果你正在運行官方的linuxlinux-lts內核,那麼該補丁被已經應用了(請確保你使用的是最新內核)。如果您正在運行另一個內核,您可能需要手動應用補丁。

注意: 幾個Ryzen用戶(請參閱這個Reddit帖子)已經測試了該補丁,並且可以確認它有效,使GPU直通性能接近原生性能。

進一步調整[編輯 | 編輯原始碼]

Red Hat的虛擬化調試和優化指南(中文)中提供了更專業的VM調優技巧。 如果您遇到以下情況,本指南可能特別有用:

  • 在從虛擬中下載/上傳期間,主機CPU負載較高,請參閱「橋接零複製傳輸」了解可能的修復。
  • 儘管使用了virtio-net,但是在下載/上傳期間,訪客網絡速度有限制。請參閱「多隊列virtio-net」以獲得可能的修復。
  • 在沒有達到負載極限的情況下,虛擬機在高I/O下卡頓。請參閱「多隊列virtio-scsi」以獲得獲得可能的修復。

特殊步驟[編輯 | 編輯原始碼]

某些機器需要特定的配置調整才能正常工作。 如果您在的宿主機或虛擬機不能正常工作,請查看您的系統是否符合以下情況之一,並嘗試相應地調整配置。

宿主機和客戶機使用相同的GPU[編輯 | 編輯原始碼]

這篇文章的某些內容需要擴充。

原因: A number of users have been having issues with this, it should probably be adressed by the article. (在 Talk:PCI passthrough via OVMF#Additionnal sections 中討論)

由於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-pcivfio_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[編輯 | 編輯原始碼]

這篇文章的某些內容需要擴充。

原因: This is related to VBIOS issues and should be moved into a separate section regarding VBIOS compatibility. (在 Talk:PCI passthrough via OVMF#UEFI (OVMF) Compatibility in VBIOS 中討論)

標記為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-glassAURlooking-glass-gitAUR 安裝。

警告: 如果你在使用looking-glass-gitAUR時出現問題,請選擇looking-glassAUR。git版本並不受上游支持,可能在任何時候破損。只有帶有版本號的發布受到支持,並且保證能與Windows客戶機上的主機程序一起使用。

當客戶機配置完成之後,您可以通過如下方式運行運行客戶端:

$ 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'>
<vendor id='0x[Before Colon]'/>
<product id='0x[After Colon]'/>
</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/sh

virsh attach-device [VM-Name] [USBdevice]

[VM-Name]替換為您的虛擬機名稱,名稱可以在virt-manager下看到。另外,將[USBdevice]替換為您希望傳遞的設備的.xml文件的完整路徑。為多個設備添加額外的行。例如,這是我的腳本:

/home/ajmar/.VFIOinput/input_attach.sh
#!/bin/sh

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

警告: 如果有人可以訪問您的虛擬機,則此方法並不安全,因為他們可以打開文件並讀取您的ROOT密碼。建議使用SSH keys

您可能還希望使用快捷鍵來執行腳本文件。在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 指向臨時位置
注意: 可以使用QEMU的默認SeaBIOS而不是OVMF,但不推薦使用它,因為它可能會導致直通設置出現問題。

建議通過使用virtio驅動程序並進一步配置,以增強性能,參閱QEMU

您還可能必須使用-cpu host,kvm=off參數將主機的CPU型號信息轉發給虛擬機,並欺騙Nvidia和其他製造商的設備驅動程序使用的虛擬化檢測,防止驅動程序拒絕在虛擬機中運行。

直通其它設備[編輯 | 編輯原始碼]

USB控制器[編輯 | 編輯原始碼]

如果你的主板具有多個USB控制器並映射到不同的組,則可以直通這些控制器而不是單個USB設備。直通整個控制器有如下的優勢:

  • 如果設備在某些操作(例如正在進行更新的手機)的過程中斷開或更改ID,虛擬機不會突然丟失設備。
  • 由該控制器管理的任何USB端口都由虛擬機直接處理,並且可以將其設備拔出,重新插入和更改,而無需通知管理程序。
  • 如果啟動VM時通常直通給虛擬機的其中一個USB設備丟失,Libvirt將不會抱怨。

與GPU不同,大多數USB控制器的驅動程序不需要任何特定配置即可在VM上運行,並且通常可以在主機和客戶機系統之間來回傳遞控制而不會產生任何副作用。

警告: 確保您的USB控制器支持Reset。#直通不支持Reset的設備

您可以使用以下命令找出哪個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主機設備列表中即可。不需要其他配置。

注意: 如果您的USB控制器不支持Reset,不在單獨的組中,或者無法直通,您仍然可以通過udev規則達到類似的目的。參見[1],它允許連接到指定USB端口的任何設備自動連接到虛擬機。

使用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.000:1d.0中的其他兩個控制器沒有這個問題,可以毫無問題地直通。

注意: 如果你的AMD顯卡無法正常Reset,參閱#AMD顯卡直通後無法重啟虛擬機

完整的例子[編輯 | 編輯原始碼]

如果您在配置的時候遇到了麻煩,您可以參考完整的配置示例。一些用戶在那裡描述了它們的配置步驟,或許您可以從中發現一些技巧。

疑難解答[編輯 | 編輯原始碼]

如果您遇到的問題並沒有在下面列出,您也可以參考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]"錯誤[編輯 | 編輯原始碼]

這篇文章的某些內容需要擴充。

原因: This error is actually related to the boot_vgs issue and should be merged together with everything else concerning GPU ROMs. (在 Talk:PCI passthrough via OVMF#UEFI (OVMF) Compatibility in VBIOS 中討論)

查看這篇文章:

如果你仍然遇到錯誤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
注意: 可能需要同時設置內核參數 video=efifb:off[2]

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.

警告: Failure during flashing may "brick" your GPU - recovery may be possible, but rarely easy and often requires additional hardware. DO NOT flash VBIOS images for other GPU models (different boards may use different VBIOSes, clocks, fan configuration). If it breaks, you get to keep all the pieces.

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
警告: DO NOT interrupt the flashing process, even if it looks like it's stuck. Flashing should take about a minute on most GPUs, but may take longer.

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的詳細信息,請閱讀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/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

AMD顯卡直通後無法重啟虛擬機[編輯 | 編輯原始碼]

某些AMD顯卡在Windows虛擬機中可能無法正常Reset,導致虛擬機無法重新啟動。

您可以通過在關閉虛擬機之前手動安全移除顯卡(就像移除U盤那樣)來避免這個問題。當您安裝VirtIO驅動程序映像中的guest-agent之後,您就可以在安全移除菜單中看到相關的設備。

如果您希望自動完成這項任務,請參閱[5]

另請參閱[編輯 | 編輯原始碼]