導讀:本文摘自王柏生、謝廣軍撰寫的《深度探索Linux系統虛擬化:原理與實現》一書,介紹了CPU虛擬化的基本概念,探討了x86架構在虛擬化時面臨的障礙,以及為支援CPU虛擬化,Intel在硬體層面實現的擴充套件VMX。同時,介紹了在VMX擴充套件支援下,虛擬CPU從Host模式到Guest模式,再回到Host模式的完整生命週期。
Gerald J. Popek和Robert P. Goldberg在1974年發表的論文“Formal Requirements for Virtualizable Third Generation Architectures”中提出了虛擬化的3個條件:
01 陷入和模擬模型
為了滿足Gerald J. Popek和Robert P. Goldberg提出的虛擬化的3個條件,一個典型的解決方案是陷入和模擬(Trap and Emulate)模型。
一般來說,處理器分為兩種執行模式:系統模式和使用者模式。相應地,CPU的指令也分為特權指令和非特權指令。特權指令只能在系統模式執行,如果在使用者模式執行就將觸發處理器異常。作業系統允許核心執行在系統模式,因為核心需要管理系統資源,需要執行特權指令,而普通的使用者程式則執行在使用者模式。
在陷入和模擬模型下,虛擬機器的使用者程式仍然執行在使用者模式,但是虛擬機器的核心也將執行在使用者模式,這種方式稱為特權級壓縮(Ring Compression)。在這種方式下,虛擬機器中的非特權指令直接執行在處理器上,滿足了虛擬化標準中高效的要求,即大部分指令無須VMM干預直接在處理器上執行。但是,當虛擬機器執行特權指令時,因為是在使用者模式下執行,將觸發處理器異常,從而陷入VMM中,由VMM代理虛擬機器完成系統資源的訪問,即所謂的模擬(emulate)。如此,又滿足了虛擬化標準中VMM控制系統資源的要求,虛擬機器將不會因為可以直接執行特權指令而修改宿主機的資源,從而破壞宿主機的環境。
02 x86架構虛擬化的障礙
Gerald J. Popek和Robert P. Goldberg指出,修改系統資源的,或者在不同模式下行為有不同表現的,都屬於敏感指令。在虛擬化場景下,VMM需要監測這些敏感指令。一個支援虛擬化的體系架構的敏感指令都屬於特權指令,即在非特權級別執行這些敏感指令時CPU會丟擲異常,進入VMM的異常處理函式,從而實現了控制VM訪問敏感資源的目的。
但是,x86架構恰恰不能滿足這個準則。x86架構並不是所有的敏感指令都是特權指令,有些敏感指令在非特權模式下執行時並不會丟擲異常,此時VMM就無法攔截處理VM的行為了。我們以修改FLAGS暫存器中的IF(Interrupt Flag)為例,我們首先使用指令pushf將FLAGS暫存器的內容壓到棧中,然後將棧頂的IF清零,最後使用popf指令從棧中恢復FLAGS暫存器。如果虛擬機器核心沒有執行在ring 0,x86的CPU並不會丟擲異常,而只是默默地忽略指令popf,因此虛擬機器關閉IF的目的並沒有生效。
有人提出半虛擬化的解決方案,即修改Guest的程式碼,但是這不符合虛擬化的透明準則。後來,人們提出了二進位制翻譯的方案,包括靜態翻譯和動態翻譯。靜態翻譯就是在執行前掃描整個可執行檔案,對敏感指令進行翻譯,形成一個新的檔案。然而,靜態翻譯必須提前處理,而且對於有些指令只有在執行時才會產生的副作用,無法靜態處理。於是,動態翻譯應運而生,即在執行時以程式碼塊為單元動態地修改二進位制程式碼。動態翻譯在很多VMM中得到應用,而且最佳化的效果非常不錯。
03 VMX
雖然大家從軟體層面採用了多種方案來解決x86架構在虛擬化時遇到的問題,但是這些解決方案除了引入了額外的開銷外,還給VMM的實現帶來了巨大的複雜性。於是,Intel嘗試從硬體層面解決這個問題。Intel並沒有將那些非特權的敏感指令修改為特權指令,因為並不是所有的特權指令都需要攔截處理。舉一個典型的例子,每當作業系統核心切換程序時,都會切換cr3暫存器,使其指向當前執行程序的頁表。但是,當使用影子頁表進行GVA到HPA的對映時,VMM模組需要捕獲Guest每一次設定cr3暫存器的操作,使其指向影子頁表。而當啟用了硬體層面的EPT支援後,cr3暫存器不再需要指向影子頁表,其仍然指向Guest的程序的頁表。因此,VMM無須再捕捉Guest設定cr3暫存器的操作,也就是說,雖然寫cr3暫存器是一個特權操作,但這個操作不需要陷入VMM。
Intel開發了VT技術以支援虛擬化,為CPU增加了Virtual-Machine Extensions,簡稱VMX。一旦啟動了CPU的VMX支援,CPU將提供兩種執行模式:VMX Root Mode和VMX non-Root Mode,每一種模式都支援ring 0 ~ ring 3。VMM執行在VMX Root Mode,除了支援VMX外,VMX Root Mode和普通的模式並無本質區別。VM執行在VMX non-Root Mode,Guest無須再採用特權級壓縮方式,Guest kernel可以直接執行在VMX non-Root Mode的ring 0中,如圖1所示。
圖1 VMX執行模式
處於VMX Root Mode的VMM可以透過執行CPU提供的虛擬化指令VMLaunch切換到VMX non-Root Mode,因為這個過程相當於進入Guest,所以通常也被稱為VM entry。當Guest內部執行了敏感指令,比如某些I/O操作後,將觸發CPU發生陷入的動作,從VMX non-Root Mode切換回VMX Root Mode,這個過程相當於退出VM,所以也稱為VM exit。然後VMM將對Guest 的操作進行模擬。相比於將Guest的核心也執行在使用者模式(ring 1 ~ ring 3)的方式,支援VMX的CPU有以下3點不同:
1)運行於Guest模式時,Guest使用者空間的系統呼叫直接陷入Guest模式的核心空間,而不再是陷入Host模式的核心空間。
2)對於外部中斷,因為需要由VMM控制系統的資源,所以處於Guest模式的CPU收到外部中斷後,則觸發CPU從Guest模式退出到Host模式,由Host核心處理外部中斷。處理完中斷後,再重新切入Guest模式。為了提高I/O效率,Intel支援外設透傳模式,在這種模式下,Guest不必產生VM exit,“裝置虛擬化”一章將討論這種特殊方式。
3)不再是所有的特權指令都會導致處於Guest模式的CPU發生VM exit,僅當執行敏感指令時才會導致CPU從Guest模式陷入Host模式,因為有的特權指令並不需要由VMM介入處理。
如同一個CPU可以分時執行多個任務一樣,每個任務有自己的上下文,由排程器在排程時切換上下文,從而實現同一個CPU同時執行多個任務。在虛擬化場景下,同一個物理CPU“一人分飾多角”,分時執行著Host及Guest,在不同模式間按需切換,因此,不同模式也需要儲存自己的上下文。為此,VMX設計了一個儲存上下文的資料結構:VMCS。每一個Guest都有一個VMCS例項,當物理CPU載入了不同的VMCS時,將執行不同的Guest如圖2所示。
圖2 多個Guest切換
VMCS中主要儲存著兩大類資料,一類是狀態,包括Host的狀態和Guest的狀態,另外一類是控制Guest執行時的行為。其中:
在建立VCPU時,KVM模組將為每個VCPU申請一個VMCS,每次CPU準備切入Guest模式時,將設定其VMCS指標指向即將切入的Guest對應的VMCS例項:
並不是所有的狀態都由CPU自動儲存與恢復,我們還需要考慮效率。以cr2暫存器為例,大多數時候,從Guest退出Host到再次進入Guest期間,Host並不會改變cr2暫存器的值,而且寫cr2的開銷很大,如果每次VM entry時都更新一次cr2,除了浪費CPU的算力毫無意義。因此,將這些狀態交給VMM,由軟體自行控制更為合理。
04 VCPU生命週期
對於每個虛擬處理器(VCPU),VMM使用一個執行緒來代表VCPU這個實體。在Guest運轉過程中,每個VCPU基本都在如圖3所示的狀態中不斷地轉換。
圖3 VCPU生命週期
下面是KVM切入、切出Guest的程式碼:
在從Guest退出時,KVM模組首先呼叫函式kvm_handle_exit嘗試在核心空間處理Guest退出。函式kvm_handle_exit有個約定,如果在核心空間可以成功處理虛擬機器退出,或者是因為其他干擾比如外部中斷導致虛擬機器退出等無須切換到Host的使用者空間,則返回1;否則返回0,表示需要求助KVM的使用者空間處理虛擬機器退出,比如需要KVM使用者空間的模擬裝置處理外設請求。
如果核心空間成功處理了虛擬機器的退出,則函式kvm_handle_exit返回1,在上述程式碼中即直接跳轉到標籤again處,然後程式流程會再次切入Guest。如果函式kvm_handle_exit返回0,則函式vmx_vcpu_run結束執行,CPU從核心空間返回到使用者空間,以kvmtool為例,其相關程式碼片段如下:
根據程式碼可見,kvmtool發起進入Guest的程式碼處於一個for的無限迴圈中。當從KVM核心空間返回使用者空間後,kvmtool在使用者空間處理Guest的請求,比如呼叫模擬裝置處理I/O請求。在處理完Guest的請求後,重新進入下一輪for迴圈,kvmtool再次請求KVM模組切入Guest。
關於作者:王柏生,資深技術專家,先後就職於中科院軟體所、紅旗Linux和百度,現任百度主任架構師。在作業系統、虛擬化技術、分散式系統、雲計算、自動駕駛等相關領域耕耘多年,有著豐富的實踐經驗。著有暢銷書《深度探索Linux作業系統》(2013年出版)。
謝廣軍,計算機專業博士,畢業於南開大學計算機系。資深技術專家,有多年的IT行業工作經驗。現擔任百度智慧雲副總經理,負責雲計算相關產品的研發。多年來一直從事作業系統、虛擬化技術、分散式系統、大資料、雲計算等相關領域的研發工作,實踐經驗豐富。
*本文經出版社授權釋出,更多關於虛擬化技術的內容推薦閱讀《深度探索Linux系統虛擬化:原理與實現》。