在虛擬化中,為了實現vCPU,既要模擬CPU的運行,又要記錄vCPU的狀態(包括對vCPU運行的控制信息),在Intel x86處理器的VMX(Virtual Machine Extension)功能中,通過引入根運行模式(VMX root operation)和非根模式(VMX non-root operation),直接讓vCPU運行在邏輯CPU上,在軟體上省去了對vCPU運行的模擬,同時也大大提升了性能。剩下的就是對vCPU狀態的記錄了,為此Intel引入了VMCS(Virtual Machine Control Structure)功能。

VMCS(Virtual Machine Control Structure)是Intel x86處理器中實現CPU虛擬化,記錄vCPU狀態的一個關鍵數據結構。該數據結構主要包含如下六方面的信息:

  1. Guest-state area,即vCPU的狀態信息,包括vCPU的基本運行環境(如通用寄存器等)和一些非寄存器信息,如當前vCPU是否接收中斷,是否有掛起的exception,VMCS的狀態等等。
  2. Host-state area,即主機物理CPU的狀態信息,因為物理CPU是在主機CPU和vCPU之間來回切換運行的,所以在VMCS中既要記錄vCPU的狀態,也需要記錄主機CPU的狀態,這樣vCPU才能有足夠的信息恢復到原來主機CPU的狀態,繼續主機CPU的運行。其包含的具體信息和前面記錄的vCPU的狀態信息大體相同。
  3. VM-execution control fields,該方面信息主要用於對vCPU的運行行為進行控制,這是VMM對vCPU進行配置最複雜的一部分,如
    1. 控制vCPU在接收到某些中斷事件的時候,是否直接在vCPU中處理掉,即虛擬機直接處理掉該中斷事件還是需要退出到VMM中,讓VMM去處理該中斷事件。
    2. 是否使用EPT(Extended Page Table)功能。
    3. 是否允許vCPU直接訪問某些I/O口,MSR寄存器等資源。
    4. vCPU執行某些指令的時候,是否會出發VM Exit等等。
  4. VM-exit control fields,即對VM Exit的行為進行控制,如VM Exit的時候對vCPU來說需要保存哪些MSR寄存器,對於主機CPU來說需要恢復哪些MSR寄存器。
  5. VM-entry control fields,即對VM Entry的行為進行控制,如需要保存和恢復哪些MSR寄存器,是否需要向vCPU注入中斷和異常等事件(VM Exit的時候不需要向主機CPU注入中斷/異常事件,因為可以讓那些事件直接觸發VM Exit)。
  6. VM-exit information fields,即記錄下發生VM Exit發生的原因及一些必要的信息,方便於VMM對VM Exit事件進行處理,如vCPU訪問了特權資源造成VM Exit,則在該區域中,會記錄下這個特權資源的類型,如I/O地址,內存地址或者是MSR寄存器,並且也會記錄下該特權資源的地址,好讓VMM對該特權資源進行模擬。

VMCS有點類似於Linux進程中的上下問,只是它保存的是vCPU/CPU的狀態和控制信息,而不是進程/線程的狀態和控制信息。

每個vCPU都有自己的一份VMCS數據結構,並且在同一個VM中的vCPU可能會通過VMCS中的指針共享一些數據,如I/O bitmap,MSR bitmap和EPT表等。VMCS的大小需要查看VMX capability MSR IA32_VMX_BASIC寄存器來決定,不同的處理器可能要求的不一樣,最大不超過4KB。

每個VMCS都會有一個8位元組的頭部,前面4位元組主要用來表示VMCS的版本號並且表示VMCS是否為shadow VMCS。然後從偏移量8開始,就是VMCS的數據區,不同版本號的VMCS數據區分布會不一樣。

為了為不同版本的VMCS(數據分布有區別)提供統一的、安全的訪問介面,VMX功能提供了VMREAD和VMWRITE兩個指令,用於對VMCS數據結構進行訪問,該指令的第一個操作數就是VMCS數據結構中各個數據域的索引(注意:並不是偏移量),在Intel SDM(Software Developer Manuel)手冊上,會定義各個數據區域的索引值。

當VMCS數據結構被load到邏輯CPU上(即執行VMPTRLD指令)後,處理器並沒法通過普通的內存訪問指令去訪問VMCS,如果那樣做的話,會引起處理器報錯,唯一可用的方法就是通過VMREAD和VMWRITE指令去訪問。

VMCS數據結構也會記錄下當前vCPU的狀態,基本上VMCS的狀態由三個域來表示:

  1. Active或Inactive,表示該VMCS是否處於激活狀態,即對於邏輯CPU來說,該VMCS是否是直接可用的,有點類似於Linux進程的可運行狀態(雖然可運行,但並不意味著現在就必須運行)
  2. Current或Non Current,該狀態表示當前邏輯CPU執行VMX指令(如VMCLEAR,VMLAUNCH,VMRESUME等)時的目標VMCS(或者可以理解為目標vCPU),這些VMX指令的目標都是狀態為Current的VMCS所代表的vCPU。
  3. Clear或者Launched,用於表示VMCS所對應的vCPU是否有在邏輯CPU上執行過,如果沒有,則為Clear狀態,如果有,則為Launched狀態,Launched狀態的VMCS也可以通過VMCLEAR指令恢復到Clear狀態。當一個vCPU從一個邏輯CPU遷移到另外一個邏輯CPU的時候,需要先將相應VMCS的狀態恢復到Clear,撇清該VMCS根原本邏輯CPU的關係,然後在另外一個邏輯CPU上執行VMPTRLD,將該VMCS和新的邏輯CPU建立聯繫。

對於VMM而言,配置並運行一個VM的最基本步驟如下所示:

  1. 根據IA32_VMX_BASIC MSR寄存器提供的VMCS region大小信息,在內存中創建一個4KB對齊的VMCS region(為了防止被直接內存訪問到,可以將該page frame從頁表中刪除);
  2. 根據IA32_VMX_BASIC MSR寄存器提供的VMCS版本信息,初始化新創建的VMCS region中的版本信息,並且將VMCS前4位元組的bit31設置為0(表示該VMCS不是shadow VMCS);
  3. 針對新創建的VMCS region執行VMCLEAR指令,這會對新創建的VMCS region進行初始化,並且將VMCS的狀態設置為Clear;
  4. 針對新創建的VMCS region執行VMPTRLD指令,該指令會將新創建的VMCS region設置為邏輯CPU的當前VMCS,即新創建的VMCS從原來的Not Current狀態變為Current狀態,後面所有的VMX相關指令操作的對象都將是新創建的VMCS;
  5. 通過一系列的VMWRITE指令對VMCS中的Host-state area進行初始化,該區域將記錄VM返回到VMM時,或者是VM Entry不成功時,邏輯CPU的狀態及執行位置等信息;
  6. 通過VMWRITE指令對VMCS中VM-Exit control field,VM-Entry control field和VM execution control field等區域進行設置;
  7. 通過WMWRITE指令對VMCS中guest state area區域進行設置,該區域表示了VM中vCPU的執行時vCPU的狀態,起始執行位置等信息;
  8. 執行VMLAUNCH指令,開始vCPU的執行。通過VMM需要判斷VMLAUNCH的返回結果,確定vCPU是否真正被執行,還是因為某些邏輯衝突導致vCPU沒有被執行就返回。

注意:在設置VMCS的各個數據區域的時候,是有順序講究的,需要先設置Host-state area,這樣可以防止後邊步驟出現問題,CPU無法返回到正確的狀態和位置,另外頁可以減少後面設置對Host CPU狀態的干擾。

vCPU的直奔執行邏輯如下圖所示:

不同的VMCS的切換就像Linux中不同的進程在邏輯CPU上切換,通過時分復用的方式共享著物理CPU的資源。

推薦閱讀:

相关文章