概述

Linux 核心包含各種程式碼,用於在 Microsoft 的 Hyper-V 虛擬機器監控程式上作為完全啟用的來賓執行。Hyper-V 主要由裸機虛擬機器監控程式以及在父分割槽中執行的虛擬機器管理服務組成(大致相當於 KVM 和 QEMU 等)。來賓 VM 在子分割槽中執行。 在本文件中,對 Hyper-V 的引用通常包括虛擬機器監控程式和 VMM 服務,而不區分哪些功能由哪個元件提供。

Hyper-V 在 x86/x64 和 arm64 架構上執行,並且兩種架構都支援 Linux 來賓。除非另有說明,否則 Hyper-V 的功能和行為在兩種架構上通常是相同的。

Linux 來賓與 Hyper-V 的通訊

Linux 來賓透過四種不同的方式與 Hyper-V 通訊

  • 隱式陷阱:按照 x86/x64 或 arm64 架構的定義,某些來賓操作會陷阱到 Hyper-V。Hyper-V 模擬該操作並將控制權返回給來賓。這種行為通常對 Linux 核心不可見。

  • 顯式超呼叫:Linux 對 Hyper-V 進行顯式函式呼叫,傳遞引數。Hyper-V 執行請求的操作並將控制權返回給呼叫者。引數在處理器暫存器中或 Linux 來賓和 Hyper-V 之間共享的記憶體中傳遞。在 x86/x64 上,超呼叫使用 Hyper-V 特定的呼叫序列。在 arm64 上,超呼叫使用 ARM 標準 SMCCC 呼叫序列。

  • 合成暫存器訪問:Hyper-V 實現了各種合成暫存器。在 x86/x64 上,這些暫存器以 MSR 的形式出現在來賓中,並且 Linux 核心可以使用 x86/x64 架構定義的正常機制來讀取或寫入這些 MSR。在 arm64 上,必須使用顯式超呼叫來訪問這些合成暫存器。

  • VMBus:VMBus 是一個更高層次的軟體構造,它建立在其他 3 個機制之上。它是 Hyper-V 主機和 Linux 來賓之間的訊息傳遞介面。它使用 Hyper-V 和來賓之間共享的記憶體,以及各種信令機制。

前三種通訊機制記錄在 Hyper-V 頂層功能規範 (TLFS) 中。TLFS 描述了 Hyper-V 的一般功能,並提供了有關超呼叫和合成暫存器的詳細資訊。TLFS 目前僅針對 x86/x64 架構編寫。

VMBus 沒有記錄。本文件提供了 VMBus 的高階概述及其工作方式,但詳細資訊只能從程式碼中辨別出來。

共享記憶體

Hyper-V 和 Linux 之間的許多方面通訊都基於共享記憶體。 這種共享通常透過以下方式完成

  • Linux 使用標準的 Linux 機制從其物理地址空間分配記憶體。

  • Linux 告訴 Hyper-V 分配記憶體的來賓物理地址 (GPA)。 許多共享區域都保持為 1 頁,以便單個 GPA 足夠。 較大的共享區域需要 GPA 列表,這些列表通常不需要在來賓物理地址空間中連續。 如何告訴 Hyper-V 有關 GPA 或 GPA 列表的方式各不相同。 在某些情況下,單個 GPA 會寫入合成暫存器。 在其他情況下,GPA 或 GPA 列表會在 VMBus 訊息中傳送。

  • Hyper-V 將 GPA 轉換為“實際”物理記憶體地址,並建立一個虛擬對映,可用於訪問該記憶體。

  • Linux 稍後可以透過告訴 Hyper-V 將共享 GPA 設定為零來撤銷之前建立的共享。

Hyper-V 的頁面大小為 4 KB。 傳達給 Hyper-V 的 GPA 可以採用頁碼的形式,並且始終描述 4 KB 的範圍。 由於 x86/x64 上的 Linux 來賓頁面大小也為 4 KB,因此從來賓頁面到 Hyper-V 頁面的對映是 1 對 1 的。 在 arm64 上,Hyper-V 支援具有 arm64 架構定義的 4/16/64 KB 頁面的來賓。 如果 Linux 使用 16 或 64 KB 頁面,則 Linux 程式碼必須小心,只能以 4 KB 頁面的形式與 Hyper-V 通訊。 HV_HYP_PAGE_SIZE 和相關宏用於與 Hyper-V 通訊的程式碼中,以便它在所有配置中都能正常工作。

如 TLFS 中所述,在 Hyper-V 和 Linux 來賓之間共享的幾個記憶體頁面是“覆蓋”頁面。 使用覆蓋頁面,Linux 使用分配來賓記憶體並告訴 Hyper-V 已分配記憶體的 GPA 的常用方法。 但 Hyper-V 隨後會將該物理記憶體頁面替換為它已分配的頁面,並且原始物理記憶體頁面在來賓 VM 中不再可訪問。 Linux 可以像訪問最初分配的記憶體一樣正常訪問該記憶體。“覆蓋”行為僅在 Linux 最初建立共享並插入覆蓋頁面時才可見,因為該頁面的內容(如 Linux 所見)發生了變化。 同樣,如果 Linux 撤銷共享,內容也會發生變化,在這種情況下,Hyper-V 會刪除覆蓋頁面,並且最初由 Linux 分配的來賓頁面再次變得可見。

在 Linux 對 kdump 核心或任何其他核心執行 kexec 之前,應撤銷與 Hyper-V 共享的記憶體。Hyper-V 可能會在新的核心將共享頁面用於其他目的後修改共享頁面或刪除覆蓋頁面,從而損壞新的核心。Hyper-V 不提供“設定所有內容”操作來賓 VM,因此 Linux 程式碼必須在執行 kexec 之前單獨撤銷所有共享。請參閱 hv_kexec_handler() 和 hv_crash_handler()。 但是,崩潰/panic 路徑仍然存在清理漏洞,因為某些共享頁面是使用每個 CPU 的合成暫存器設定的,並且沒有機制可以撤銷執行 panic 路徑的 CPU 以外的 CPU 的共享頁面。

CPU 管理

Hyper-V 無法從正在執行的 VM 熱新增或熱刪除 CPU。 但是,Windows Server 2019 Hyper-V 及更早版本可能會為來賓提供 ACPI 表,指示的 CPU 數量多於 VM 中實際存在的 CPU 數量。 像往常一樣,Linux 將這些額外的 CPU 視為潛在的熱新增 CPU,並將其報告為這樣,即使 Hyper-V 永遠不會實際熱新增它們。 從 Windows Server 2022 Hyper-V 開始,ACPI 表僅反映 VM 中實際存在的 CPU,因此 Linux 不會報告任何熱新增 CPU。

可以使用正常的 Linux 機制將 Linux 來賓 CPU 離線,前提是沒有 VMBus 通道中斷分配給該 CPU。 有關如何重新分配 VMBus 通道中斷以允許將 CPU 離線的更多詳細資訊,請參閱有關 VMBus 中斷的部分。

32 位和 64 位

在 x86/x64 上,Hyper-V 支援 32 位和 64 位來賓,並且 Linux 將構建並以任一版本執行。 雖然 32 位版本預計可以工作,但很少使用,並且可能遭受未檢測到的迴歸。

在 arm64 上,Hyper-V 僅支援 64 位來賓。

位元組順序

Hyper-V 和來賓 VM 之間的所有通訊都使用小端格式,無論是在 x86/x64 還是 arm64 上。 Hyper-V 不支援 arm64 上的大端格式,並且 Linux 程式碼在訪問與 Hyper-V 共享的資料時不使用位元組順序宏。

版本控制

當前的 Linux 核心可以與 Windows Server 2012 Hyper-V 的舊版 Hyper-V 正常執行。 對在 Windows Server 2008/2008 R2 的原始 Hyper-V 版本上執行的支援已被刪除。

Hyper-V 上的 Linux 來賓會在 dmesg 中輸出其執行的 Hyper-V 版本。 此版本採用 Windows 內部版本號的形式,僅用於顯示目的。 Linux 程式碼不會在執行時測試此版本號,以確定可用的特性和功能。 Hyper-V 透過 Hyper-V 提供給來賓的合成 MSR 中的標誌指示特性/功能可用性,並且來賓程式碼會測試這些標誌。

VMBus 有其自己的協議版本,該版本在來賓到 Hyper-V 的初始 VMBus 連線期間協商。 此版本號也在啟動期間輸出到 dmesg。 程式碼中的一些位置會檢查此版本號,以確定是否存在特定功能。

此外,VMBus 上的每個合成裝置也有一個協議版本,該版本與 VMBus 協議版本分開。 這些合成裝置的裝置驅動程式通常會協商裝置協議版本,並且可能會測試該協議版本以確定是否存在特定裝置功能。

程式碼打包

與 Hyper-V 相關的程式碼出現在 Linux 核心程式碼樹中的三個主要區域

  1. drivers/hv

  2. arch/x86/hyperv 和 arch/arm64/hyperv

  3. 各個裝置驅動程式區域,例如 drivers/scsi、drivers/net、drivers/clocksource 等。

一些雜項檔案出現在其他位置。 請參閱 MAINTAINERS 檔案中“Hyper-V/Azure 核心和驅動程式”和“適用於 HyperV 合成影片裝置的 DRM 驅動程式”下的完整列表。

只有在設定了 CONFIG_HYPERV 時,才會構建 #1 和 #2 中的程式碼。 同樣,只有在設定了 CONFIG_HYPERV 時,才會構建大多數與 Hyper-V 相關的驅動程式的程式碼。

#1 和 #3 中的大多數與 Hyper-V 相關的程式碼都可以構建為模組。 #2 中的特定於架構的程式碼必須內建。 此外,drivers/hv/hv_common.c 是跨架構的通用底層程式碼,必須內建。