Hypervisor 為來賓提供呼叫機制。 這類呼叫稱為 hypercall。 每個超級呼叫都會定義一組輸入和/或輸出參數。 這些參數是根據記憶體型資料結構來指定。 輸入和輸出資料結構的所有元素都會填補至最多 8 個位元組的自然界限 (也就是說,兩個位元組元素必須位於兩個位元組界限上,依此類推)。
第二個 hypercall 呼叫慣例可以選擇性地用於 hypercall 的子集,特別是具有兩個或更少輸入參數且沒有輸出參數的 hypercall。 使用此呼叫慣例時,輸入參數會在一般用途暫存器中傳遞。
第三個 hypercall 呼叫慣例可以選擇性地用於輸入參數區塊最多為 112 個位元組的 hypercall 子集。 使用此呼叫慣例時,輸入參數會在暫存器中傳遞,包括易失性 XMM 暫存器。
輸入和輸出資料結構都必須放在 8 位元組界限的記憶體中,並填補為 8 個位元組的倍數大小。 填充區域內的值會被 Hypervisor 忽略。
對於輸出,虛擬機管理程式可以 (但不保證) 覆寫填補區域。 如果它覆寫填充區域,它將寫入零。
Hypercall 類別
超調用有兩類:簡單和重複(“重複”的縮寫)。 簡單的超級呼叫會執行單一作業,並具有一組固定大小的輸入和輸出參數。 代表超級呼叫的動作類似於一系列簡單的超級呼叫。 除了一組固定大小的輸入和輸出參數之外,rep hypercalls 還涉及固定大小的輸入和/或輸出元素清單。
當呼叫端一開始叫用代表超級呼叫時,它會指定代表計數,指出輸入和/或輸出參數清單中的元素數目。 呼叫端也會指定代表開始索引,指出應該取用的下一個輸入和/或輸出元素。 Hypervisor 會依清單順序處理 rep 參數,也就是增加元素索引。
對於 rep hypercall 的後續叫用,rep 開始索引會指出已完成的元素數目,並與代表計數值搭配,還剩下多少元素。 例如,如果呼叫端指定代表計數 25,而且在時間限制內只完成 20 次反覆專案,則在將代表開始索引更新為 20 之後,超級呼叫會將控制權傳回呼叫端虛擬處理器。 當重新執行超級呼叫時,Hypervisor 會在元素 20 繼續,並完成剩餘的 5 個元素。
如果在處理元素時遇到錯誤,則會提供適當的狀態碼以及完成次數計數,指出在遇到錯誤之前成功處理的元素數量。 假設指定的 hypercall 控制單字有效 (請參閱下列內容) ,且輸入/輸出參數清單可供存取,則保證 Hypervisor 會嘗試至少一次代表,但不需要在將控制權傳回給呼叫端之前處理整個清單。
Hypercall 繼續
超呼叫可以被認為是需要許多週期的複雜指令。 Hypervisor 會嘗試將 Hypercall 執行限制為 50μs 或更短,再將控制權傳回給叫用 Hypercall 的虛擬處理器。 一些超級呼叫操作非常複雜,以至於很難保證 50μs。 因此,Hypervisor 依賴某些 hypercall 的 hypercall 延續機制,包括所有代表 hypercall 表單。
hypercall 繼續機制對呼叫端來說大多是透明的。 如果 hypercall 無法在規定的時間限制內完成,控制權會傳回給呼叫端,但指令指標不會推進超過叫用 hypercall 的指令。 這允許處理擱置中斷並排程其他虛擬處理器。 當原始呼叫執行緒繼續執行時,它會重新執行 hypercall 指令,並朝著完成作業前進。
大多數簡單的超級呼叫都保證在規定的時間內完成。 不過,少量的簡單超級呼叫可能需要更多時間。 這些 hypercall 會以類似於代表 hypercall 的方式使用 hypercall 繼續。 在這種情況下,操作涉及兩個或多個內部狀態。 第一次呼叫會將物件 (例如分割區或虛擬處理器) 置於一個狀態,在重複呼叫之後,狀態最終會轉換成終端狀態。 針對遵循此模式的每個超呼叫,都會描述中間內部狀態的可見副作用。
Hypercall 原子性和排序
除非另有說明,否則超呼叫所執行的動作對於所有其他客體作業 (例如,在客體內執行的指令) ,以及系統上執行的所有其他超呼叫都是不可部分完成的。 簡單的 hypercall 會執行單一原子動作;代表 Hypercall 會執行多個獨立的原子動作。
使用 hypercall 延續的簡單 hypercall 可能涉及外部可見的多個內部狀態。 這類呼叫包含多個原子作業。
每個超呼叫動作都可以讀取輸入參數和/或寫入結果。 每個動作的輸入可以在進行超呼叫之後和執行動作之前的任何時間以任何粒度讀取。 與每個動作相關聯的結果 (也就是輸出參數) 可以在執行動作之後和 hypercall 傳回之前的任何時間以任何粒度寫入。
來賓必須避免檢查和/或操作與執行 hypercall 相關的任何輸入或輸出參數。 雖然執行 hypercall 的虛擬處理器將無法執行此動作 (,因為其客體執行會暫停,直到 hypercall 傳回為止) ,但沒有任何內容可以阻止其他虛擬處理器執行此動作。 以這種方式運作的客體可能會當機或導致其分割區內損壞。
合法的 Hypercall 環境
Hypercall只能從最具特殊許可權的客體處理器模式叫用。 在 x64 平台上,這表示目前許可權層級 (CPL) 為零的保護模式。 雖然實模式程式碼以零的有效 CPL 執行,但不允許在實際模式中進行超呼叫。 嘗試在非法處理器模式中叫用超呼叫會在 x64 上產生 #UD (未定義的作業) 例外狀況,並在 ARM64 上產生未定義的指令例外狀況。
所有 hypercall 都應該透過架構定義的 hypercall 介面叫用 (請參閱下方) 。 嘗試以任何其他方式叫用超級呼叫 (例如,將程式代碼從超級呼叫字碼頁複製到替代位置,並從該位置執行) 可能會導致未定義的作業 (#UD) 例外狀況。 不保證 Hypervisor 會提供此例外狀況。
對齊要求
呼叫端必須指定輸入和/或輸出參數的 64 位客體實體位址 (GPA) 。 GPA 指標必須以 8 位元組對齊。 如果 Hypercall 不涉及任何輸入或輸出參數,Hypervisor 會忽略對應的 GPA 指標。
輸入和輸出參數清單不能重疊或跨頁界限。 Hypercall 輸入和輸出頁面應該是 GPA 頁面,而不是「重迭」頁面。 如果虛擬處理器將輸入參數寫入重迭頁面,並在此頁面內指定 GPA,則未定義輸入參數清單的 Hypervisor 存取權。
Hypervisor 會驗證呼叫分割區在執行要求的 Hypercall 之前,是否可以從輸入頁面讀取。 此驗證包含兩個檢查:對應指定的 GPA,並將 GPA 標示為可讀。 如果其中任一測試失敗,Hypervisor 會產生記憶體攔截訊息。 對於具有輸出參數的 Hypercall,Hypervisor 會驗證分割區是否可以寫入輸出頁面。 此驗證包含兩個檢查:對應指定的 GPA ,並將 GPA 標示為可寫入。
Hypercall 輸入
呼叫端會藉稱為 hypercall 輸入值的 64 位值來指定 hypercall。 其格式如下:
| 領域 | 位元 | 提供的資訊 |
|---|---|---|
| 呼叫代碼 | 15-0 | 指定要求的 hypercall |
| 快速 | 16 | 指定 Hypercall 是否使用暫存器型呼叫慣例:0 = 記憶體型,1 = 暫存器型 |
| 可變標頭大小 | 26-17 | 變數標頭的大小,以 QWORDS 為單位。 |
| RsvdZ | 30-27 | 必須為零 |
| 巢狀 | 31 | 指定巢狀環境中的 L0 Hypervisor 應該處理 Hypercall。 |
| 代表次數 | 43-32 | 代表總數(對於代表呼叫,否則必須為零) |
| RsvdZ | 47-44 | 必須為零 |
| 代表開始索引 | 59-48 | 起始索引 (對於代表呼叫,否則必須為零) |
| RsvdZ | 63-60 | 必須為零 |
對於代表超級呼叫,代表計數欄位會指出代表總數。 代表開始索引指出相對於清單開頭的特定重複 (零表示要處理清單中的第一個元素)。 因此,rep count 值必須一律大於rep 開始索引。
Hypercall 暫存器慣例 (x86/x64)
在 x86/x64 上,當 Fast 旗標為零時,超呼叫輸入的暫存器對應如下:
| x64 | x86 | 提供的資訊 |
|---|---|---|
| RCX | EDX:EAX | Hypercall 輸入值 |
| 環己美環酸酯(RDX) | EBX:ECX | 輸入參數 GPA |
| R8 | 編輯:ESI | 輸出參數 GPA |
超呼叫輸入值會與指向輸入和輸出參數的 GPA 一起傳遞在暫存器中。
暫存器對應取決於呼叫端是在 32 位 (x86) 或 64 位 (x64) 模式中執行。 Hypervisor 會根據 EFER 的值來判斷呼叫端的模式。LMA 和 CS.L. 如果這兩個旗標都已設定,則會假設呼叫端是 64 位呼叫端。
當 Fast 旗標為 1 時,超呼叫輸入的註冊對應:
| x64 | x86 | 提供的資訊 |
|---|---|---|
| RCX | EDX:EAX | Hypercall 輸入值 |
| 環己美環酸酯(RDX) | EBX:ECX | 輸入參數 |
| R8 | 編輯:ESI | 輸出參數 |
hypercall 輸入值會與輸入參數一起傳遞在暫存器中。
Hypercall 暫存器慣例 (ARM64 SMCCC)
在 ARM64 上,超呼叫會使用「HVC #0」指令執行。 呼叫遵循 ARM64 SMCCC (SMC 呼叫慣例) 。
Hypercall 輸入的暫存器對應如下:
| Register | 提供的資訊 |
|---|---|
| X0 | SMCCC 函數標識符 |
| 1個 | Hypercall 輸入值 |
| 2 個 | 輸入參數 |
| X3 | 輸出參數 |
X0 中的 SMCCC 函數標識符遵循以下格式:
| 位元 | 領域 | 價值觀 | Description |
|---|---|---|---|
| 31 | 讓步呼籲 | 0 | 一律為 0 |
| 30 | 呼叫慣例 | 1 | 1 用於 HVC64 呼叫慣例 |
| 29:24 | 服務呼叫類型 | 6 | 6 用於供應商特定的 Hypervisor 服務呼叫 |
| 23:16 | 已保留 | 0 | 已保留,必須為零 (Res0) |
| 15:0 | 功能編號 | 1 | 1 表示 HV 呼叫代碼定義在 X1 中 |
完整的函式識別碼格式: 0x46000001
Hypercall 暫存器慣例 (ARM64 HVC #1)
基於歷史原因,ARM64 Hypervisor 介面也支援不同的呼叫慣例。 超調用是使用 “HVC #1” 指令執行。 建議您針對新程式碼使用 SMCCC 呼叫慣例。
Hypercall 輸入的暫存器對應如下:
| Register | 提供的資訊 |
|---|---|
| X0 | Hypercall 輸入值 |
| 1個 | 輸入參數 |
| 2 個 | 輸出參數 |
可變大小的 Hypercall 輸入標頭
大部分的 hypercall 輸入標頭都有固定大小。 因此,從客體傳遞至 Hypervisor 的標頭資料量是由 Hypercall 程式碼隱含指定,而且不需要個別指定。 不過,某些超呼叫需要可變數量的標頭資料。 這些超呼叫通常具有固定大小的輸入標頭,以及可變大小的其他標頭輸入。
可變大小的標頭類似於固定的 hypercall 輸入 (對齊至 8 個位元組,大小為 8 個位元組的倍數) 。 呼叫端必須指定它提供多少資料作為輸入標頭。 此大小會作為 hypercall 輸入值的一部分提供 (請參閱上表中的「變數標頭大小」)。
由於固定標頭大小是隱含的,因此不會提供標頭大小總計,而是在輸入控制項中只提供變數部分:
Variable Header Bytes = {Total Header Bytes - sizeof(Fixed Header)} rounded up to nearest multiple of 8
Variable Header Size = Variable Header Bytes / 8
為未明確記錄為接受變數大小輸入標頭的 hypercall 指定非零變數標頭大小是非法的。 在這種情況下,超呼叫將導致傳回碼 HV_STATUS_INVALID_HYPERCALL_INPUT。
對於接受可變大小輸入標頭的超呼叫的指定叫用,所有標頭輸入可能會完全符合固定大小的標頭。 在這種情況下,可變大小的輸入標頭大小為零,而超呼叫輸入中的對應位應該設定為零。
在所有其他方面,接受可變大小輸入標頭的超呼叫在呼叫慣例方面類似於固定大小的輸入標頭超呼叫。 可變大小的標頭 hypercall 也可以額外支援代表語意。 在這種情況下,rep 元素以通常的方式位於標頭之後,但標頭的總大小包括固定部分和可變部分。 所有其他規則保持不變,例如第一個 rep 元素必須對齊 8 位元組。
XMM 快速 Hypercall 輸入 (x86/x64)
在 x86/x64 平臺上,Hypervisor 支援使用 XMM 快速 Hypercall,這可讓某些 Hypercall 利用快速 Hypercall 介面的改善效能,即使它們需要兩個以上的輸入參數也一樣。 XMM 快速 hypercall 介面會使用六個 XMM 暫存器,讓呼叫端傳遞大小高達 112 個位元組的輸入參數區塊。
XMM快速Hypercall介面的可用性透過「Hypervisor 功能識別」CPUID分葉(0x40000003)指出:
- 位 4:支援透過 XMM 暫存器傳遞 hypercall 輸入。
請注意,有一個單獨的旗標來指示對 XMM 快速輸出的支持。 當 Hypervisor 未指出可用性時,任何嘗試使用此介面都會導致 #UD 錯誤。
暫存器映射(僅輸入)
| x64 | x86 | 提供的資訊 |
|---|---|---|
| RCX | EDX:EAX | Hypercall 輸入值 |
| 環己美環酸酯(RDX) | EBX:ECX | 輸入參數區塊 |
| R8 | 編輯:ESI | 輸入參數區塊 |
| XMM0 | XMM0 | 輸入參數區塊 |
| XMM1 | XMM1 | 輸入參數區塊 |
| XMM2 | XMM2 | 輸入參數區塊 |
| XMM3 的 | XMM3 的 | 輸入參數區塊 |
| XMM4 | XMM4 | 輸入參數區塊 |
| XMM5 | XMM5 | 輸入參數區塊 |
hypercall 輸入值會與輸入參數一起傳遞在暫存器中。 暫存器對應取決於呼叫端是在 32 位 (x86) 或 64 位 (x64) 模式中執行。 Hypervisor 會根據 EFER 的值來判斷呼叫端的模式。LMA 和 CS.L. 如果這兩個旗標都已設定,則會假設呼叫端是 64 位呼叫端。 如果輸入參數區塊小於 112 個位元組,則會忽略暫存器中的任何額外位元組。
暫存器快速呼叫輸入 (ARM64 SMCCC)
在 ARM64 平臺上,Hypervisor 支援使用暫存器快速 Hypercall,這可讓某些 HyperCall 利用快速 HyperCall 介面的改善效能,即使它們需要兩個以上的輸入參數也一樣。 暫存器快速 hypercall 介面會使用 15 個一般用途暫存器,讓呼叫端傳遞大小高達 120 個位元組的輸入參數區塊。
暫存器映射(僅輸入)
| Register | 提供的資訊 |
|---|---|
| X0 | SMCCC 函數標識符 |
| 1個 | Hypercall 輸入值 |
| X2 - X17 | 輸入參數區塊 |
如果輸入參數區塊小於 120 個位元組,則會忽略暫存器中的任何額外位元組。
暫存器快速呼叫輸入 (ARM64 HVC #1)
暫存器快速 hypercall 介面會使用十六個一般用途暫存器,讓呼叫端傳遞大小高達 128 位元組的輸入參數區塊。
暫存器映射(僅輸入)
| Register | 提供的資訊 |
|---|---|
| X0 | Hypercall 輸入值 |
| 1 號 - 17 號 | 輸入參數區塊 |
如果輸入參數區塊小於 128 個位元組,則會忽略暫存器中的任何額外位元組。
Hypercall 輸出
所有 hypercall 都會傳回稱為 hypercall 結果值的 64 位值。 其格式如下:
| 領域 | 位元 | 評論 |
|---|---|---|
| Result | 15-0 |
HV_STATUS 指示成功或失敗的程式碼 |
| 回覆 | 31-16 | 呼叫端應該忽略這些位中的值 |
| 已完成代表 | 43-32 | 成功完成的次數 |
| RsvdZ | 63-40 | 呼叫端應該忽略這些位中的值 |
對於代表超級呼叫,代表完成欄位是完成的代表總數,而不是相對於代表開始索引。 例如,如果呼叫者指定代表開始索引 5,代表計數為 10,則代表完成欄位會在成功完成時指出 10。
超呼叫結果值會在暫存器中傳回。
在 x64 上,暫存器對應取決於呼叫端是在 32 位 (x86) 或 64 位 (x64) 模式中執行 (請參閱上文) 。 hypercall 輸出的暫存器對應如下:
| x64 | x86 | 提供的資訊 |
|---|---|---|
| RAX | EDX:EAX | Hypercall 結果值 |
在 ARM64 上,hypercall 輸出的暫存器對應如下:
| Register | 提供的資訊 |
|---|---|
| X0 | Hypercall 結果值 |
XMM 快速 Hypercall 輸出 (x86/x64)
類似於 Hypervisor 支援 XMM 快速 hypercall 輸入的方式,可以共用相同的暫存器來傳回輸出。 這僅在 x64 平台上受支援。
透過 XMM 暫存器傳回輸出的能力會透過「Hypervisor 功能識別」CPUID 分葉 (0x40000003) 指出:
- 位 15:支援透過 XMM 暫存器傳回 hypercall 輸出。
請注意,有一個單獨的旗標來指示對 XMM 快速輸入的支援。 當 Hypervisor 未指出可用性時,任何嘗試使用此介面都會導致 #UD 錯誤。
暫存器映射(輸入和輸出)
未用於傳遞輸入參數的暫存器可用於傳回輸出。 換句話說,如果輸入參數區塊小於 112 個位元組 (四捨五入至最接近的 16 個位元組對齊區塊) ,則其餘暫存器會傳回 hypercall 輸出。
| x64 | 提供的資訊 |
|---|---|
| 環己美環酸酯(RDX) | 輸入或輸出區塊 |
| R8 | 輸入或輸出區塊 |
| XMM0 | 輸入或輸出區塊 |
| XMM1 | 輸入或輸出區塊 |
| XMM2 | 輸入或輸出區塊 |
| XMM3 的 | 輸入或輸出區塊 |
| XMM4 | 輸入或輸出區塊 |
| XMM5 | 輸入或輸出區塊 |
例如,如果輸入參數區塊的大小為 20 個位元組,則 Hypervisor 會忽略下列 12 個位元組。 其餘 80 個位元組會包含 hypercall 輸出 (,如果適用) 。
暫存器快速呼叫輸出 (ARM64 SMCCC)
在 ARM64 平臺上,類似於 Hypervisor 支援暫存器快速 Hypercall 輸入的方式,可以共用相同的暫存器來傳回輸出。
暫存器映射(輸入和輸出)
未用於傳遞輸入參數的暫存器可用於傳回輸出。 換句話說,如果輸入參數區塊小於 120 個位元組 (四捨五入至最接近的 8 個位元組對齊區塊) ,則其餘暫存器會傳回 hypercall 輸出。
| Register | 提供的資訊 |
|---|---|
| X2 - X17 | 輸入或輸出區塊 |
例如,如果輸入參數區塊的大小為 20 個位元組,Hypervisor 會忽略下列 4 個位元組。 其餘 96 個位元組會包含 hypercall 輸出 (,如果適用)。
暫存器快速呼叫輸出 (ARM64 HVC #1)
與 SMCCC 版本類似,HVC #1 介面使用相同的暫存器來傳回輸出。
暫存器映射(輸入和輸出)
未用於傳遞輸入參數的暫存器可用於傳回輸出。 換句話說,如果輸入參數區塊小於 128 個位元組 (四捨五入至最接近的 8 個位元組對齊區塊) ,則其餘暫存器會傳回 hypercall 輸出。
| Register | 提供的資訊 |
|---|---|
| 1 號 - 17 號 | 輸入或輸出區塊 |
例如,如果輸入參數區塊的大小為 20 個位元組,Hypervisor 會忽略下列 4 個位元組。 其餘 104 個位元組會包含 hypercall 輸出 (如果適用) 。
揮發性暫存器 (x86/x64)
Hypercalls 只會在下列情況下修改指定的暫存器值:
- RAX (x64) 和 EDX:EAX (x86) 一律會以 hypercall 結果值和輸出參數 (如果有的話) 覆寫。
- 代表超級呼叫將使用新的代表起始索引修改 RCX (x64) 和 EDX:EAX (x86)。
- HvCallSetVpRegisters 可以修改該 Hypercall 支援的任何暫存器。
- RDX、R8 和 XMM0 到 XMM5 用於快速 hypercall 輸入時,保持不變。 不過,您可以修改用於快速 hypercall 輸出的暫存器,包括 RDX、R8 和 XMM0 到 XMM5。 Hyper-V 只會修改這些暫存器,以取得快速 hypercall 輸出,限制為 x64。
揮發性暫存器 (ARM64 SMCCC)
Hypercalls 只會在下列情況下修改指定的暫存器值:
- X0 一律會以 hypercall 結果值和輸出參數 (如果有的話) 覆寫。
- 代表超呼叫會使用新的代表開始索引修改 X1。
- HvCallSetVpRegisters 可以修改該 Hypercall 支援的任何暫存器。
- X2 - X17 用於快速 hypercall 輸入時,會保持不變。 不過,您可以修改用於快速 hypercall 輸出的暫存器,包括 X2 - X17。 Hyper-V 只會修改這些暫存器,以取得快速 hypercall 輸出。
揮發性暫存器 (ARM64 HVC #1)
Hypercalls 只會在下列情況下修改指定的暫存器值:
- X0 一律會以 hypercall 結果值和輸出參數 (如果有的話) 覆寫。
- 代表超級呼叫會使用新的代表開始索引修改 X0。
- HvCallSetVpRegisters 可以修改該 Hypercall 支援的任何暫存器。
- X1 - X17 用於快速 hypercall 輸入時,會保持不變。 不過,您可以修改用於快速 hypercall 輸出的暫存器,包括 X1 - X17。 Hyper-V 只會修改這些暫存器,以取得快速 hypercall 輸出。
超級通話限制
Hypercall 可能具有相關聯的限制,必須滿足這些限制才能執行其預期功能。 如果未符合所有限制,超級呼叫將會終止,並顯示適當的錯誤。 如果有的話,將列出以下限制:
- 呼叫分割區必須具有特定專用權
- 正在處理的分割區必須處於特定狀態(例如「作用中」)
Hypercall 狀態碼
每個 hypercall 都會記錄為傳回包含數個欄位的輸出值。 狀態值欄位 (類型 HV_STATUS) 可用來指出呼叫是否成功或失敗。
失敗 Hypercall 的輸出參數有效性
除非另有明確說明,否則當 hypercall 失敗時 (亦即,hypercall 結果值的結果欄位包含 ) 以外的 HV_STATUS_SUCCESS值,所有輸出參數的內容都是不確定的,呼叫端不應檢查。 只有當超級呼叫成功時,所有適當的輸出參數才會包含有效的預期結果。
錯誤條件的排序
Hypervisor 偵測及報告錯誤狀況的順序未定義。 換句話說,如果存在多個錯誤,Hypervisor 必須選擇要報告的錯誤條件。 應優先處理那些提供更高安全性的錯誤代碼,目的是防止 Hypervisor 向缺乏足夠權限的呼叫者洩露資訊。 例如,狀態碼 HV_STATUS_ACCESS_DENIED 是偏好的狀態碼,而不是純粹根據權限顯示某些內容或狀態資訊的狀態碼。
常見的 Hypercall 狀態碼
數個結果代碼是所有 hypercall 通用的,因此不會個別記錄每個 hypercall。 這些包括下列各項:
| 狀態碼 | 錯誤狀況 |
|---|---|
HV_STATUS_SUCCESS |
呼叫成功。 |
HV_STATUS_INVALID_HYPERCALL_CODE |
無法辨識 hypercall 程式碼。 |
HV_STATUS_INVALID_HYPERCALL_INPUT |
代表計數不正確 (例如,非零代表計數會傳遞至非代表呼叫,或將零代表計數傳遞至代表呼叫)。 |
| 代表起始指數不小於代表次數。 | |
| 指定 hypercall 輸入值中的保留位為非零。 | |
HV_STATUS_INVALID_ALIGNMENT |
指定的輸入或輸出 GPA 指標未對齊至 8 個位元組。 |
| 指定的輸入或輸出參數會列出跨頁。 | |
| 輸入或輸出 GPA 指標不在 GPA 空間的範圍內。 |
回覆碼 HV_STATUS_SUCCESS 指出未偵測到任何錯誤狀況。
報告客體作業系統身分識別
在分割區內執行的客體 OS 必須先將其簽章和版本寫入 MSR (HV_X64_MSR_GUEST_OS_ID/HvRegisterGuestOsId) ,才能叫用 Hypercall,以識別自己給 Hypervisor。 此 MSR 是全分割區的,並在所有虛擬處理器之間共用。
此暫存器的值最初為零。
在 x86/x64 上,必須先將非零值寫入客體 OS ID MSR,才能啟用 Hypercall 字碼頁 (請參閱 建立 Hypercall 介面 (x86/x64) ) 。 如果此暫存器隨後歸零,則會停用 hypercall 字碼頁。
在 ARM64 上,必須先將非零值寫入客體 OS ID MSR,才能叫用超級呼叫程式代碼。 例外狀況是 HvCallSetVpRegisters/HvCallGetVpRegisters 超級呼叫。 請參閱各自的文件以取得更多資訊。
#define HV_X64_MSR_GUEST_OS_ID 0x40000000
#define HvRegisterGuestOsId 0x00090002
在 ARM64 上,僅支援 HvRegisterGuestOsId,必須使用開機處理器上的 HvCallSetVpRegisters 超呼叫來撰寫。
專屬作業系統的客體作業系統身分識別
以下是此 MSR 的建議編碼。 某些欄位可能不適用於某些客體作業系統。
| 位元 | 領域 | Description |
|---|---|---|
| 15:0 | 組建編號 | 指出作業系統的組建編號 |
| 23:16 | 服務版本 | 指出服務版本 (例如,「Service Pack」編號) |
| 31:24 | 小版本 | 指出作業系統的次要版本 |
| 39:32 | 主要版本 | 表示作業系統的主要版本 |
| 47:40 | 作業系統識別碼 | 指出作業系統變體。 編碼是供應商獨有的。 Microsoft 作業系統的編碼方式如下:0=未定義、1=MS-DOS®、2=Windows® 3.x、3=Windows® 9x、4=Windows® NT(和衍生產品)、5=Windows® CE |
| 62:48 | 廠商 ID | 指出客體作業系統廠商。 保留值 0。 請參閱下面的供應商列表。 |
| 63 | OS 類型 | 指出作業系統類型。 值 0 代表專屬 (封閉原始碼) 作業系統。 值 1 代表開放原始碼作業系統。 |
廠商值是由 Microsoft 配置。 若要要求新的廠商,請在 GitHub 虛擬化文件存放庫 ()https://aka.ms/VirtualizationDocumentationIssuesTLFS 上提出問題。
| 供應商 | 價值觀 |
|---|---|
| Microsoft | 0x0001 |
| HPE | 0x0002 |
| BlackBerry | 0x0003 |
| 蘭康 | 0x0200 |
開放原始碼作業系統的客體作業系統身分識別 MSR
下列編碼是為想要符合此規格的開放原始碼作業系統廠商提供指引。 建議開源作業系統採用以下約定。
| 位元 | 領域 | Description |
|---|---|---|
| 15:0 | 組建編號 | 發行版特定資訊 (例如,組建編號)。 |
| 47:16 | 版本 | 上游核心版本資訊。 |
| 55:48 | 作業系統識別碼 | 其他供應商資訊 |
| 62:56 | OS 類型 | 作業系統類型(例如 Linux、FreeBSD 等)。 請參閱下面的已知作業系統類型清單 |
| 63 | 開放原始碼 | 值 1 表示開放原始碼作業系統。 |
作業系統類型值由 Microsoft 配置。 若要要求新的作業系統類型,請在 GitHub 虛擬化文件存放庫 ()https://aka.ms/VirtualizationDocumentationIssuesTLFS 上提出問題。
| OS 類型 | 價值觀 |
|---|---|
| Linux | 0x1 |
| FreeBSD | 0x2 |
| Xen | 0x3 |
| 伊盧莫斯 | 0x4 |
建立 Hypercall 介面 (x86/x64)
在 x86/x64 上,Hypercalls 會使用特殊操作碼來叫用。 由於此操作碼在虛擬化實現之間不同,因此 Hypervisor 有必要抽象化此差異。 這是通過一個特殊的超級呼叫頁面完成的。 此頁面由 Hypervisor 提供,並顯示在來賓的 GPA 空間內。 訪客必須透過對訪客 Hypercall MSR 進行程式設計來指定頁面的位置。
#define HV_X64_MSR_HYPERCALL 0x40000001
| 位元 | Description | Attributes |
|---|---|---|
| 63:12 | Hypercall GPFN — 指出Hypercall頁面的訪客實體頁碼 | 讀取/寫入 |
| 11:2 | RsvdP。 讀取時應忽略位元,並在寫入時保留位元。 | 已保留 |
| 1 | 已鎖定。 指出 MSR 是否不可變。 如果設定,則會鎖定此 MSR,從而防止重新定位 hypercall 頁面。 設定後,只有系統重置才能清除位元。 | 讀取/寫入 |
| 0 | 啟用 Hypercall 頁面 | 讀取/寫入 |
hypercall 頁面可以放置在來賓 GPA 空間內的任何位置,但必須對齊頁面。 如果來賓嘗試將 hypercall 頁面移至 GPA 空間的界限之外,則寫入 MSR 時會產生 #GP 錯誤。
此 MSR 是全分割區的 MSR。 換句話說,它是由分割區中的所有虛擬處理器共用的。 如果一個虛擬處理器成功寫入 MSR,另一個虛擬處理器會讀取相同的值。
在啟用 Hypercall 頁面之前,客體 OS 必須將其版本簽章寫入個別的 MSR (HV_X64_MSR_GUEST_OS_ID) 來報告其身分識別。 如果未指定客體 OS 身分識別,則嘗試啟用 Hypercall 將會失敗。 即使寫入 1,啟用位也將保持零。 此外,如果在啟用超級呼叫頁面之後,客體 OS 身分識別清除為零,則會停用。
超呼叫頁面顯示為 GPA 空間的「覆蓋」;也就是說,它涵蓋了映射到 GPA 範圍的任何其他內容。 其內容可由訪客讀取和執行。 嘗試寫入 hypercall 頁面會導致保護 (#GP) 例外狀況。 啟用 Hypercall 頁面之後,叫用 Hypercall 只會牽涉到頁面開頭的呼叫。
以下是建立超級呼叫頁面所涉及的步驟詳細清單:
- 客體讀取CPUID枝葉1,並通過檢查暫存器ECX的位31來確定Hypervisor是否存在。
- 客體讀取CPUID枝葉0x40000000以確定最大Hypervisor CPUID枝葉(在暫存器EAX中傳回),並讀取CPUID枝葉0x40000001以確定介面簽章(在暫存器EAX中傳回)。 它驗證最大葉值至少為0x40000005,並且介面簽章是否等於「Hv#1」。 此簽章表示
HV_X64_MSR_GUEST_OS_ID、HV_X64_MSR_HYPERCALL和HV_X64_MSR_VP_INDEX會實作。 - 如果該暫存器為零,客體會將其 OS 身分識別寫入 MSR
HV_X64_MSR_GUEST_OS_ID。 - 訪客讀取 Hypercall MSR (
HV_X64_MSR_HYPERCALL)。 - 訪客檢查啟用超級呼叫頁面位。 如果設定了,則介面已經處於活動狀態,應省略步驟6和7。
- 來賓在其 GPA 空間內找到一個頁面,最好是未被 RAM、MMIO 等佔用的頁面。 如果頁面被佔用,訪客應避免將基礎頁面用於其他目的。
- 客體會將新值寫入 Hypercall MSR (
HV_X64_MSR_HYPERCALL) ,其中包含步驟 6 中的 GPA,並設定 [啟用 Hypercall 頁面] 位以啟用介面。 - 客體會建立可執行的 VA 對應至 hypercall 頁面 GPA。
- 客體會查閱 CPUID 分葉0x40000003,以判斷哪些 Hypervisor 設施可供其使用。 建立介面後,訪客可以發起超級呼叫。 若要這樣做,它會根據 hypercall 通訊協定填入暫存器,並向 hypercall 頁面的開頭發出 CALL。 來賓應該假設 hypercall 頁面會執行相當於近回傳 (0xC3) 的動作,以傳回呼叫端。 因此,必須使用有效的堆疊叫用 Hypercall。
建立 Hypercall 介面 (ARM64)
由於 ARM64 原生支援 HVC 指令,因此 Hypervisor 不需要額外的設定即可啟用 Hypercall。
擴展的 Hypercall 介面
呼叫代碼高於0x8000的超級呼叫稱為擴展超級呼叫。 擴充 Hypercall 使用與一般 HyperCall 相同的呼叫慣例,而且從客體 VM 的觀點來看看起來相同。 擴充 Hypercall 會在 Hyper-V Hypervisor 內以不同的方式在內部處理。
擴充的 Hypercall 功能可以使用 HvExtCallQueryCapabilities 來查詢。