在2003年出現的Xen,使用了另外的一種半虛擬化的方案來解決x86架構下CPU的敏感指令問題。主要采用Hypercall技術。Guest OS的部分代碼被改變,從而使Guest OS會將和特權指令相關的操作都轉換為發給VMM的Hypercall(超級調用),由VMM繼續進行處理。而Hypercall支持的批處理和異步這兩種優化方式,使得通過Hypercall能得到近似於物理機的速度。
1、Hypercall技術
對於x86體系結構CPU,Xen使用超級調用來替換被監控的操作,其中包括x86架構下的敏感指令。Xen所采用的超級替換的方法是一種全新的設計理念:它將問題的中心,由VMM移向Guest OS自身,通過主動的方式由Guest OS去處理這些指令,而不是被移交給VMM做處理,在這種設計理念下,修改Guest OS內核。
能修改Guest OS是半虛擬化的一個技術核心。通過修改Guest OS的內核。使Guest OS明確知道自己是運行在1環上,而不是通常OS的0環,有效的避免了虛擬化的執行沖突問題。Guest OS也清楚VMM給自己提供了一個虛擬的寄存器組,並能通過其他方式去訪問他們,避免了訪問沖突的問題。
解決了敏感指令問題只是解決了x86架構下的半虛擬化的第一步。運行在1環的操作系統沒有權限執行的指令,交給0環的VMM來處理,這個很大程度上與應用程序的系統調用很類似:系統調用的作用是把應用程序無權執行的指令交給操作系統完成。因此,Xen向Guest OS提供了一套“系統調用”。以方便Guest OS調用,這部分”系統調用“就是超級調用Hypercall。
超級調用Hypercall的機制使用,不僅使x86架構的指令虛擬化得以實現,也為后面的內存虛擬化和I/O虛擬化提供了新的思路和方法,超級調用和事件通道是整個半虛擬化的基礎。
下面我們來看看半虛擬化情況下整體的訪問流程圖,如圖所示。
CPU半虛擬化技術
上圖明確的顯示了Hypercall的調用位置,在Xen中,各組件通信方式如下所示,Hypercall的調用性質是同步的。其他Xen的通信方式幾乎都是異步的。
其中,在虛擬機和Xen的通信過程中,如果虛擬機需要調用敏感指令,會主動向虛擬機監控器發起Hypercall調用。Hypercall就如同傳統操作系統下的系統調用,監管程序通過它向其上各虛擬機提供各種服務,如MMU 更新、Domain0 操作請求和虛擬處理器狀態等。
下圖顯示了半虛擬化模式下的特權模式:
在x86架構下,原生系統和半虛擬化環境下存在差異。原生環境下,CPU有4個特權級(ring0--ring3),操作系統是處於最高級別的ring0,應用程序處於最低級別的ring3。而在半虛擬化環境下,虛擬機監視器是處於最高級別的ring0,操作系統是處於中級級別的ring1,應用程序處於最低級別的ring3。
只有特權級別為1 的代碼(准虛擬化Guest VM 的內核)才能向Xen 發送Hypercall 請求,以防止應用程序(特權級3)的錯誤調用導致對系統可能的破壞。因此,只有運行在特權級1 的虛擬機操作系統內核才能申請Hypercall。但是,一些Xen 專用的特別程序,如xend 或xe也需要有Hypervisor 的服務來完成特殊的操作,如生成一個新的GuestVM 等,這在Xen Linux 中是通過一個稱為privcmd 的內核驅動程序實現。應用程序通過ioctl 向該驅動程序提出服務請求,運行在虛擬機內核(特權級1)的privcmd 驅動程序再將服務請求以Hypercall 形式轉向Hypervisor,並由后者真正完成生成新Guest VM 的動作。
上圖中顯示了Hypercall所在的位置,Hypercall位於圖中右上方,內核向Hypervisor發起調用的哪里。Xen啟用130號中斷向量端口(十六進制的82H)作為超級調用的中斷號。這一個中斷向量的DPL被設置為類型為1,類型為中斷門。這樣,超級調用能夠由處於特權級1的客戶機操作系統發起,而不能從用戶態發起。
另外,在x86指令集的指令中,有17 條指令不能有效的在ring 1 特權級上運行,Hypercall 的存在解決了這些指令不能正常執行的問題。
Hypercall 機制中,在32 位x86 架構下,Hypercall 通過int0x82陷阱(Trap)指令實現,因為傳統操作系統本身並不使用int0x82 (Linux 使用int 0x80 作為系統調用指令,int 0x82 並未使用)。
int0x82包括:
-
超級調用號:xen/include/public/xen.h中定義了45個超級調用,其中有7個是平台相關調用。
-
超級調用表:xen/arch/x86/x86_32/entry.S中定義了超級調用表,通過超級調用號索引就可以方便的找到對應的處理函數。
-
超級調用頁:超級調用頁是Xen為Guest OS准備的一個頁,可以做到不同Guest OS有不同的超級調用頁內容。
Hypercall 的具體功能識別號由eax 表明,而其他參數則在ebx, ecx, edx, esi 和edi 中。為了減少虛擬機和Hypervisor 之間的特權級別(Ring)切換次數,Xen 提供對Hypercall的批處理,即將幾個Hypercall 功能請求放在一個列表中由專門的Hypercall 批處理請求完成。在Xen 中,系統調用表與Hypercall 表都在entry.S 文件中被定義。
2、X86架構特權級
x86 硬件支持 4 個特權級 (Ring),一般內核運行在 Ring 0, 用戶應用運行在 Ring 3, 更小的 Ring 有比更高的 Ring 能訪問更多的系統全局資源,即更高的特權。有些指令只能在 Ring 0 才能正確執行,如 LGDT、LMSW 指令,我們稱之為特權指令;另外有些指令可以在 Ring 3 正確執行,如 SGDT、 SMSW、PUSHF/POPF,我們稱之為非特權指令。
正常模式和虛擬化兩種情況敘述下,特權模式說明如下:
正常模式:特權級別是針對段來講的,段描述符的最后兩位標識了該段所位於的特權級別,比如,中斷處理程序運行於ring0(),此時的內核程序是具有特權的,即ring0。位於ring3用戶程序可以通過系統調用的方式,int80,后特權翻轉入ring0,然后就可以順利執行中斷處理程序(好像是用戶程序調用內核程序的唯一途徑)。
虛擬化情況下:
特權解除:是指解除正常情況下運行於ring0的段,比如中斷處理程序,為了虛擬化需要,此時解除其特權,將其運行於ring1。當用戶程序通過系統調用時,其跳轉到的中斷處理程序運行於ring1。但是,在中斷處理程序中,有部分指令是必須在ring0才能執行的,此時,便會自動陷入,然后模擬。也就是說,用戶程序運行特權指令,會有兩次特權下降,一次是通過系統調用進入ring1,第二次是通過特權指令陷入進入ring0。這說明,中斷發生時的中斷處理程序還是以前的位於內核的代碼,但是其運行級別為ring1,部分指令還需要再次陷入,才能執行。另外,還有一個重要問題,就是部分敏感非特權指令無法陷入的問題:存在二進制翻譯、超級調用等方式,強迫其陷入,然后模擬。
在傳統的 X86 平台上支持虛擬化上存在如下問題 :
X86 指令集中存在 17 條敏感的非特權指令,“非特權指令”表明這些指令可以在 x86 的 ring 3 執行, 而“敏感性”說明 VMM 是不可以輕易讓客操作系統執行這些指令的。 這 17 條指令在客操作系統上的執行或者會導致系統全局狀態的破壞,如 POPF 指令,或者會導致客操作系統邏輯上的問題,如 SMSW 等讀系統狀態或控制寄存器的指令。傳統的 X86 沒法捕獲這些敏感的非特權指令。
除了那 17 條敏感的非特權指令,其他敏感的指令都是敏感的特權指令。在 x86 虛擬化環境,VMM 需要對系統資源進行統一的控制,所以其必然要占據最高的特權級,即 Ring 0, 所以為了捕獲特權指令,在傳統 x86 上一個直接可行的方法是 “Ring deprivileging”, 如將客操作系統內核的特權級從 Ring 0改為 Ring 1 或 Ring 3, 即 “消除” 客內核的特權,以低於 VMM所在的 Ring 0, 從而讓 VMM 捕獲敏感的特權指令。
3、總結
半虛擬化的思想就是,讓客戶操作系統知道自己是在虛擬機上跑的,工作在非ring0狀態,那么它原先在物理機上執行的一些特權指令,就會修改成其他方式,這種方式是可以和VMM約定好的,這就相當於,通過修改代碼把操作系統移植到一種新的架構上來,就像是定制化。所以XEN這種半虛擬化技術,客戶機操作系統都是有一個專門的定制內核版本,和x86、mips、arm這些內核版本。這樣以來,就不會有捕獲異常、翻譯、模擬的過程了,性能損耗非常低。這就是XEN這種半虛擬化架構的優勢。這也是為什么XEN半虛擬化只支持虛擬化Linux,無法虛擬化windows原因,微軟不修改代碼無法實現半虛擬化。