VM 退出(VM-exit)
當一個客體在硬體輔助虛擬化下執行時,CPU 正處於客體模式、嗡嗡地直接執行客體自己的指令。VM-exit 就是硬體把控制權猛地拽回超管理器的那一刻,因為客體剛做了某件超管理器說過它想處理的事。鏡像的操作,也就是超管理器把控制權交還給客體繼續跑,叫做 VM-entry。兩者合起來是一台執行中虛擬機的心跳:進入客體、讓它跑、在某件有趣的事上退出、把它處理好、再進入。
在機制上,VM-exit 像是一個極其笨重的陷阱。CPU 凍結客體的確切狀態、把它的暫存器存進那個控制結構(Intel 上的 VMCS)、載入超管理器存好的狀態、再跳到超管理器的處理常式——這有時被叫做「world switch(世界切換)」,因為整台機器的情境從客體世界翻到了主機世界。超管理器接著讀一個「退出原因(exit reason)」欄位,得知它究竟「為何」退出:也許客體執行了 cpuid、或碰了某個控制暫存器、或存取了映射到某個虛擬裝置的記憶體、或來了一個外部中斷。它處理那個原因——模擬裝置、注入中斷、不管是什麼——最後以一次 VM-entry 收尾,把客體恢復到它離開的那一點。
工程師為何對此念念不忘:一次 VM-exit 很昂貴,光是情境翻轉就要花數百到數千個 CPU 週期,這還沒算任何實際工作。所以虛擬化的效能,很大程度上就是「減少退出」的藝術。光這一個事實就解釋了一整窩的功能:巢狀分頁的存在,是為了讓客體更新分頁表時「不」退出;半虛擬化驅動的存在,是為了讓一次成批的退出取代上千次「逐位元組」的裝置退出;而 posted interrupt 的存在,是為了讓一個中斷不必退出就能抵達客體。若你哪天在剖析一台慢吞吞的虛擬機,先去數一數、分一分它的 VM-exit,那永遠是第一件該做的事。
客體執行 cpuid 指令,想問自己跑在什麼 CPU 上。由於超管理器想對客體呈現一個受控、一致的 CPU 身分,cpuid 被設定為會引發一次 VM-exit;超管理器填入它所選定的答案,再做一次 VM-entry,讓客體從下一條指令處繼續執行。
cpuid 觸發一次 VM-exit,好讓超管理器交給客體一個精心安排的答案,再重新進入。
每次 VM-exit 光是情境切換就要花真實的週期,所以「快速虛擬化」大致上就是「少退出」。一台每寫一個裝置位元組或每改一次分頁表就退出的虛擬機會慢如龜爬;成批處理與各種硬體功能的存在,正是為了避免這件事。