跳轉到

5.2 二進位轉譯與半虛擬化(VMware 1998、Xen 2003)#

背景:x86 不可虛擬化的突破#

如 5.1 節所述,x86 因為若干敏感指令在用戶態不 trap,長期被視為「不可虛擬化」。1998 年,Mendel Rosenblum、Edouard Bugnion、Scott Devine 等史丹佛研究者在 Palo Alto 創立 VMware(前身為 Stanford 的 DISCO 計畫,1997 年的 SOSP 論文),並在 1999 年推出 VMware Workstation,第一次在商用產品中解決了 x86 虛擬化。他們的答案不是修改硬體、也不是修改客座 OS,而是二進位轉譯(binary translation, BT):在執行期掃描客座的機器碼,把會出問題的指令序列改寫成可安全執行的等價序列。

二進位轉譯:x86 虛擬化的第一把鑰匙#

VMware 的執行模型是把客座碼分成兩種處理路徑:

客座程式碼流
   │
   ├─ 大多數指令(非敏感)→ 直接複製到轉譯快取(translation cache)
   │    (如同 AOT 複製,執行接近原生速度)
   │
   └─ 敏感/特權指令 → 由轉譯器改寫為「呼叫 VMM 的等價序列」
        例:客座的 popf → 以呼叫 VMM 服務常式取代
        例:客座寫 CR3(切換頁表)→ 改為呼叫影子頁表同步常式

關鍵技術要點:

  1. 轉譯快取(translation cache):轉譯後的程式區塊被快取,相同程式碼再次執行時不需重新轉譯。這與 JIT 的 code cache 完全同構——事實上,BT 就是一種 JIT,只是輸入是現成機器碼而非位元碼,且正確性標準是「與原碼逐副作用等價」。
  2. 找出基本區塊邊界:x86 變長指令、間接跳躍使「這段碼會跳到哪裡」靜態不可判定。VMware 採保守策略:遇到跳躍時以猜測 + 運行時檢查處理(若有間接跳到未被轉譯的位址,陷入轉譯器重來)。
  3. 自我修改碼與寫入偵測:若客座寫入已被轉譯的頁面,快取須失效。以頁面保護位元(write-protect)實現寫入 trap。
  4. 效能工程:熱路徑的直接指令以「直譯級」開銷換取「原生級」速度,整體開銷在良好情況下 5-20%,遠優於逐指令模擬。2000 年代初 VMware 進一步以二元轉譯 + 影子頁表 + VMM 裝置模型的組合達到近乎原生的 CPU 效能。

二進位轉譯的譜系其實更早:1960 年代 IBM 為新舊機型之間的程式相容(如 7090 模擬 704、S/360 模擬 1401)已使用模擬與轉譯;1990 年代 Digital 的 FX!32(1996,在 Alpha 上跑 x86 程式)與 Apple 的 68k→PowerPC 轉譯(1994,Mac OS 的 68LC040 模擬路線)證明了動態 BT 的實用性。VMware 的貢獻是把 BT 從「異構相容工具」變成「同構虛擬化武器」。這條血統延續至今:Apple 的 Rosetta 2(2020,x86→ARM)、微軟在 Windows on ARM 的 x86 模擬,都是同構/異構 BT 的現代後裔。

半虛擬化:把問題交給客座 OS 自己改#

二進位轉譯優雅但昂貴:轉譯器複雜、快取管理、自我修改碼處理都是工程深水區。另一條路的思路完全相反——乾脆修改客座 OS,讓它知道自己在虛擬機裡,主動用「hypervisor 呼叫(hypercall)」取代會出問題的敏感指令。這就是半虛擬化(paravirtualization),其集大成者是 2003 年劍橋大學 Ian Pratt 與 Keir Fraser 團隊發表的 Xen(SOSP 2003 論文《Xen and the Art of Virtualization》)。

Xen 的架構把 hypervisor(Xen 自稱 thin hypervisor,程式碼僅數萬行)置於最高特權層,客座 Linux 被修改為「Xen 核心」,其特權操作改為 hypercall(類似系統呼叫的 trap 進 hypervisor),事件通道(event channel)取代虛擬中斷,記憶體管理透過「可寫頁表」協定由客座自己維護並經 hypervisor 驗證,I/O 則以分離驅動模型(split driver):客座的 front-end 驅動與一個特權 VM(Domain 0)的 back-end 驅動以共享記憶體環(shared memory ring + grant table)交換資料,繞過了昂貴的設備模擬。

+----------------+ +----------------+
| Domain U 客座   | | Domain 0(特權)|
| 修改的核心      | | 原生驅動       |
| front-end net  | | back-end net   |
+------┬---------+ +------┬---------+
       │ 共享記憶體 ring / grant table
       └──────> Xen hypervisor(trap 進出)<──────┘
                      │
                  實體硬體

半虛擬化的利弊極為清晰:效能好(敏感操作直接變成高效的 hypercall,I/O 走共享記憶體,開銷可低於 BT 路線)且 hypervisor 簡單;代價是必須修改客座——這在開源 OS(Linux)可行,在閉源 Windows(2003 年時)不可行,且每次核心升級都要維護移植。Xen 早期因此主要服務 Linux;2003 年代後期 Xensource 對 Windows 的支援則仰賴後來的硬體輔助路線。Xen 生態的影響深遠:2006-2010 年間 AWS EC2 的基礎設施即以 Xen 為骨幹,其 split-driver 與 grant table 設計成為半虛擬 I/O 的標準範式(後來的 virtio 見 5.3 節為其精神繼承者)。

兩條路線的對比與匯流#

面向 二進位轉譯(VMware) 半虛擬化(Xen)
客座是否修改 否(完全虛擬化) 是(核心需移植)
敏感指令處理 執行前改寫 客座主動 hypercall
複雜度所在 VMM(轉譯器、快取) 客座核心移植
閉源客座支援 可 不可(除非硬體輔助)
I/O 策略 設備模擬 split driver / 共享記憶體
理論定位 以動態翻譯換取等價性 以修改介面換取效率

歷史的終局是匯流:2005-2006 年硬體輔助虛擬化(VT-x/AMD-V)普及後,兩條路線都退居輔助角色——VMware 的 BT 退為處理少數指令的特例,Xen 則轉為「硬體輔助 + 半虛擬 I/O(後續的 PVHVM 模式)」的混合體。但兩條路線留下的技術遺產至今是雲端的基礎:BT 的思想活在 CPU 模擬器(QEMU 的 TCG)、Rosetta 2 與瀏覽器 JIT 中;半虛擬化介面的思想活在 virtio、vsock、以及所有「客座感知」的雲端客制化核心中。

與編譯器/OS 的關聯#

二進位轉譯把編譯器的所有核心問題(指令選擇、暫存器配置、基本區塊與控制流分析、code cache 管理)搬到「以機器碼為輸入」的場景,且新增了「必須逐副作用等價」的正確性要求,是編譯器理論最嚴苛的應用場景之一。半虛擬化則是作業系統課程中「保護邊界與介面設計」的重新演練:它證明了一個架構洞見——與其攔截並猜測,不如重新設計介面讓合作方明說。這與編譯器領域「與其推測型別,不如讓語言提供型別資訊(如 .NET 的 CIL、WebAssembly 的型別化位元碼)」是同一個哲學。

小結#

1998 年的 VMware 與 2003 年的 Xen,分別以「動態翻譯」與「介面重設計」兩種手段攻克了 x86 虛擬化的難題,把 1970 年代的大型機技術帶回 PC 與伺服器世界,直接鋪墊了雲端運算。下一節將看到,最終解決這場拉鋸的是硬體本身——Intel VT-x 與 AMD-V,以及同時代的容器革命。