捕捉並模擬(trap-and-emulate)
捕捉並模擬(trap-and-emulate)是建構虛擬機器的經典而優雅的方法。概念很簡單:盡量讓客體直接在真實 CPU 上執行,但一旦它想做只有「老大」才能做的事,就立刻把控制權奪走,由超管理器偽造結果。這就像讓學徒親手操作真機器做所有無害的步驟,但每當學徒伸手去碰危險的操縱裝置時,師傅就瞬間接手——學徒從不知道有人介入過。
用白話步驟說明機制如下。客體的核心以為自己跑在特權模式,但超管理器其實是讓它跑在非特權模式。一般指令(加法、比較、搬移)就在 CPU 上全速執行。可是當客體執行一道特權指令——例如關閉中斷或載入分頁表暫存器——CPU 會拒絕,因為客體並非真的有特權,於是改為陷入(trap):跳進超管理器。超管理器查看客體想做什麼,模擬出一個正確又安全的結果(更新它自己的記帳),再把控制權交回,讓客體繼續,渾然不覺。幾乎一切都原生執行,只捕捉敏感的部分,再模擬那些部分。
要讓捕捉並模擬乾淨地運作,硬體必須滿足 Popek-Goldberg 的要求:每一道敏感指令(會觸碰或揭露特權狀態的指令)也必須是一道特權指令,在非特權狀態執行時會陷入。在符合此要求的 CPU 上,捕捉並模擬既正確又快速。麻煩在於老舊的 x86 不符合——有些敏感指令在非特權下不會陷入,反而默默做了錯事——這正是 x86 歷史上被稱為「不可虛擬化」的原因,也正是為何必須發明二進位轉譯與硬體輔助虛擬化來繞過它。
一個客體核心執行了「關閉中斷」這道指令。在真實 CPU 上它是非特權的,於是這道指令陷入超管理器。超管理器並沒有真的關掉主機的中斷;它只是記下一個虛擬旗標,表示「這台虛擬機器以為中斷已關」,然後讓客體繼續。客體相信自己成功了。
一道特權的客體指令陷入;超管理器模擬出一個安全的結果。
捕捉並模擬只有在「每一道敏感指令都會陷入」時才正確運作。最初的 x86 有些「敏感但非特權」的指令,在非特權下不陷入而是默默失敗,所以純粹的捕捉並模擬無法虛擬化它——這一個硬體缺口,驅動了數十年的繞道方案。