綜述
本文描述了Armv8-A AArch64的虛擬化支持。包括stage 2頁表轉(zhuǎn)換,虛擬異常,以及陷阱。本文介紹了一些基礎(chǔ)的硬件輔助虛擬化理論以及一些Hypervisor如何利用這些虛擬化特性的例子。文本不會講述某一具體的Hypervisor軟件是如何工作的以及如何開發(fā)一款Hypervisor軟件。通過閱讀本文,你可以學(xué)到兩種類型的Hypervisor以及它們是如何映射到Arm的異常級別。你將能解釋陷阱是如何工作的以及其是如何被用來進行各種模擬操作。你將能描述Hypervisor可以產(chǎn)生什么虛擬異常以及產(chǎn)生這些虛擬異常的機制。理解本文內(nèi)容需要一定基礎(chǔ),本文假定你熟悉ARMv8體系結(jié)構(gòu)的異常模型和內(nèi)存管理。
虛擬化簡介
這里我們將介紹一些基礎(chǔ)的Hypervisor和虛擬化的理論知識。如果你已經(jīng)有一定的基礎(chǔ)或是已經(jīng)熟悉了這些概念,可以跳過這部分內(nèi)容。我們用Hypervisor這個詞來定義一種負責創(chuàng)建,管理以及調(diào)度虛擬機(Virtual Machines, VMs)的軟件。
虛擬化為什么重要?
虛擬化是一種在現(xiàn)代云計算和企業(yè)基礎(chǔ)架構(gòu)中廣泛使用的技術(shù)。開發(fā)人員用虛擬機在一個硬件平臺上運行多個不同的操作系統(tǒng)來開發(fā)和測試軟件,以避免對主計算環(huán)境造成可能的破壞。虛擬化技術(shù)在服務(wù)器上非常流行,大多數(shù)面向服務(wù)器的處理器都需要支持虛擬化功能,這是因為虛擬化能給數(shù)據(jù)中心服務(wù)器帶來如下一些需要的特性:
- 隔離:利用虛擬化可以對同一個物理核上運行的虛擬機進行隔離。這使得相互間不可信的的計算環(huán)境可以共享同一套硬件環(huán)境。例如,兩個競爭對手可以共享同一個物理機器而又不能訪問對方的數(shù)據(jù)。高可用性:虛擬化可以在不同的物理機器之間無縫透明地遷移負載。這個技術(shù)廣泛用于將負載從出錯的硬件平臺遷移至其他可用平臺,以便維護和替換出錯的硬件而不影響服務(wù)。負載均衡:為了降低數(shù)據(jù)中心硬件和功耗成本,需要盡可能充分地利用硬件平臺資源。將負載均衡地遷移到不同地物理機上,有利用充分利用物理機資源,降低功耗,同時為租戶提供最佳性能。沙箱:虛擬機可以作為一個沙箱來為運行在其中的應(yīng)用屏蔽其他軟件的干擾,或者避免其干擾其他軟件。例如在虛擬機中運行特定軟件,可以避免該軟件的bug或病毒導(dǎo)致物理機器上的其他軟件損壞。
Hypervisor的兩種類型
Hypervisor通常被分成兩種類型,獨立類型Type 1和寄生類型 Type 2。我們先看看Type 2類型的Hypervisor。對于Type 2類型的Hypervisor,其寄生的宿主操作系統(tǒng)擁有對硬件平臺和資源(包括CPU和物理內(nèi)存…)的全部控制權(quán)。下圖展示了Type 2類型的Hypervisor。
宿主操作系統(tǒng),指的是直接運行在硬件平臺上并為Type 2類型的Hypervisor提供運行環(huán)境的操作系統(tǒng)。這類Hypervisor可以充分利用宿主操作系統(tǒng)對物理硬件的管理功能,而Hypervisor只需提供虛擬化相關(guān)功能即可。不知你是否使用過Virtual Box或是VMware Workstation, 這類軟件就是Type 2類型的Hypervisor。
接下來,看看獨立類型的Type 1 Hypervisor, 如下圖。這類Hypervisor沒有宿主操作系統(tǒng)。其直接運行在物理硬件之上,直接管理各種物理資源,同時管理并運行客戶機操作系統(tǒng)。
在開源社區(qū)常見的Hypervisor, Xen (Type 1) 和 KVM (Type 2)就分屬這兩種不同的類型。
全虛擬化和半虛擬化
關(guān)于虛擬機,經(jīng)典定義是:虛擬機是一個獨立的隔離的計算環(huán)境,這種計算環(huán)境讓使用者看起來就像在使用真實的物理機器一樣。盡管我們可以在基于ARM的硬件平臺上模擬真實硬件,但這通常不是最有效的做法,因此我們常常不這么做。例如,模擬一個真實的以太網(wǎng)設(shè)備是非常慢的,這是因為對任何一個模擬寄存器的訪問都會陷入到Hypervisor當中進行模擬。比起直接訪問物理寄存器來說,這種操作的代價要昂貴得多。一個替代方案是修改客戶操作系統(tǒng),使之意識到自身運行在虛擬機當中,通過在Hypervisor中模擬一個虛擬設(shè)備來給客戶機使用。以此來換取更好得I/O性能。嚴格來說,全虛擬化需要完全模擬真實硬件,性能上會比較差。開源項目Xen推進了半虛擬化,通過修改客戶機操作系統(tǒng)的核心部分使其更適合在虛擬環(huán)境中運行,以此來提高性能。
另一個使用半虛擬化的原因是早期的體系結(jié)構(gòu)并不是為虛擬化而設(shè)計的,存在虛擬化漏洞。因為虛擬化要求所有敏感指令或訪問敏感資源的指令都能被截獲模擬。對于存在虛擬化漏洞的體系結(jié)構(gòu),則需要通過半虛擬化的方案來填補漏洞。而今,大多數(shù)體系機構(gòu)都支持硬件輔助虛擬化,包括Arm。這使得操作系統(tǒng)的核心部分無需修改也能獲得較好得性能。只有少數(shù)存儲和網(wǎng)絡(luò)相關(guān)的I/O設(shè)備仍然采用半虛擬化的方案來改善性能,這類半虛擬化的方案如,virtio 和 Xen PV Bus。
虛擬機(VM)和虛擬CPU (vCPU)
有必要區(qū)分虛擬機(VM)和虛擬CPU(vCPU)。這有利于理解本文的后續(xù)部分。例如,一個內(nèi)存頁面可以分配給一個虛擬機,因此所有屬于該VM的vCPUs都可以訪問它。而一個虛擬中斷只是針對某個vCPU,因此只有該vCPU可以收到。虛擬機(VM)和虛擬CPU(vCPU)的關(guān)系如下圖所示。
注意:ARM體系結(jié)構(gòu)定義了處理單元(Processing Element, PE)一詞,現(xiàn)代CPU可能包含多個內(nèi)核或線程,PE用來指代單一的執(zhí)行單元。同樣的這里的vCPU嚴格來說應(yīng)該是vPE。
AArch64的虛擬化
對于ARMv8, Hypervisor運行在EL2異常級別。只有運行在EL2或更高異常級別的軟件才可以訪問并配置各項虛擬化功能。
- Stage 2轉(zhuǎn)換EL1/0指令和寄存器訪問注入虛擬異常
安全狀態(tài)和非安全狀態(tài)下的異常級別及可運行的軟件如下圖所示:
注意:安全狀態(tài)的EL2用灰色顯示是因為,安全狀態(tài)的EL2并不總是可用,這是Armv8.4-A引入的特性。
Stage 2 轉(zhuǎn)換
什么是Stage 2 轉(zhuǎn)換?
Stage 2 轉(zhuǎn)換允許Hypervisor控制虛擬機的內(nèi)存視圖。具體來說,其可以控制虛擬機是否可以訪問特定的某一塊物理內(nèi)存,以及該內(nèi)存塊出現(xiàn)在虛擬機內(nèi)存空間的位置。這種能力對于虛擬機的隔離和沙箱功能來說至關(guān)重要。這使得虛擬機只能看到分配給它自己的物理內(nèi)存。為了支持Stage 2 轉(zhuǎn)換, 需要增加一個頁表,我們稱之為Stage 2頁表。操作系統(tǒng)控制的頁表轉(zhuǎn)換稱之為stage 1轉(zhuǎn)換,負責將虛擬機視角的虛擬地址轉(zhuǎn)換為虛擬機視角的物理地址。而stage 2頁表由Hypervisor控制,負責將虛擬機視角的物理地址轉(zhuǎn)換為真實的物理地址。虛擬機視角的物理地址在Armv8中有特定的詞描述,叫中間物理地址(intermediate Physical Address, IPA)。
stage 2轉(zhuǎn)換表的格式和stage 1的類似,但也有些屬性的處理不太一樣,例如,判斷內(nèi)存類型 是normal 還是 device的信息被直接編碼進了表里,而不是通過查詢MAIR_ELx寄存器。
VMID
每一個虛擬機都被分配一個ID號,稱之為VMID。這個ID號用于標記某個特定的TLB項屬于哪一個VM。VMID使得不同的VM可以共享同一塊TLB緩存。VMID存儲在寄存器VTTBR_EL2中,可以是8或16比特,由VTCR_EL2.vs比特位控制,其中16比特的VMID支持是在armv8.1-A中擴展的,是可選的。需注意,EL2和EL3的地址轉(zhuǎn)換不需要VMID標記,因為它們不需要stage 2轉(zhuǎn)換。
VMID vs ASID
TLB項也可以用ASID(Address Space Identifier)標記,每個應(yīng)用都被操作系統(tǒng)分配有一個ASID,所有屬于同一個應(yīng)用的TLB項都有相同的ASID。這使得不同應(yīng)用可以共享同一塊TLB緩存。每一個VM有它自己的ASID空間。例如兩個不同的VMs同時使用ASID 5,但指的是不同的東西。對于虛擬機而言,通常VMID會結(jié)合ASID同時使用。
屬性整合和覆蓋
stage 1 和 stage 2映射都包含屬性,例如存儲類型,訪問權(quán)限等。內(nèi)存管理單元(MMU)會將兩個階段的屬性整合成一個最終屬性,整合的原則是選擇更有限制的屬性。且看如下例子:
在上面的例子中,Device屬性比起Normal屬性更具限制性,因此最終結(jié)果是Device屬性。同樣的原理,如果你將順序調(diào)換一下也不會改變最終的屬性。
屬性整合在大多數(shù)情況下都可以工作。但有些時候,例如在VM的早期啟動階段,Hypervisor希望改變默認的行為,則可以通過如下寄存器比特來實現(xiàn)。
- HCR_EL2.CD: 控制所有stage 1屬性為Non-cacheable。HCR_EL2.DC:強制所有stage 1屬性為Normal,Write-Back Cacheable。HCR_EL2.FWB (Armv8.4-A引入):使用stage 2屬性覆蓋stage 1屬性,而不是使用默認的限制性整合原則。
模擬MMIO
與物理機器的物理地址空間類似,VM的IPA地址空間包含了內(nèi)存與外圍設(shè)備兩種區(qū)域。如下圖所示:
VM使用外圍設(shè)備區(qū)域來訪問其看到的物理外圍設(shè)備,這其中包含了直通設(shè)備和虛擬外圍設(shè)備。虛擬設(shè)備完全由Hypervisor模擬,如下圖所示
一個直通設(shè)備被直接分配給VM并映射到IPA地址空間,這使得VM中的軟件可用直接訪問真實的物理硬件。一個虛擬的外圍設(shè)備由Hypervisor模擬,其stage 2的轉(zhuǎn)換項被標記為fault。雖然VM中的軟件看來其是直接與物理設(shè)備交互,但實際上這一訪問會導(dǎo)致stage 2轉(zhuǎn)換fault,從而進入相應(yīng)的異常處理程序由Hypervisor模擬。
為了模擬一個外圍設(shè)備,Hypervisor需要知道哪一個外圍設(shè)備被訪問,外圍設(shè)備的哪一個寄存器被訪問,是讀訪問還是寫訪問,訪問長度是多少,以及使用哪些寄存器來傳送數(shù)據(jù)。
當處理stage 1 faults時,F(xiàn)AR_ELx寄存器包含了觸發(fā)異常的虛擬地址。但虛擬地址不是給Hypervisor用的,Hypervisor通常不會知道客戶操作系統(tǒng)如何配置虛擬地址空間的映射。對于stage 2 faults,有一個專門的寄存器HPFAR_EL2,該寄存器會報告發(fā)生錯誤的IPA地址。IPA地址空間由Hypervisor控制,因此可用利用此寄存器里的信息來進行必要的模擬。
ESR_ELx寄存器用于報告發(fā)生異常的相關(guān)信息。當loads或stores一個通用寄存器觸發(fā)stage 2 fault時,相關(guān)異常信息由這些寄存器提供。這些信息包含了,訪問的長度,訪問的原地址或目的地址。Hypervisor可以以此來判斷對虛擬外圍設(shè)備訪問的權(quán)限。下圖展示了一個 陷入(trapping) – 模擬(emulating) 的訪問過程。
- VM里的軟件嘗試訪問虛擬外圍設(shè)備,這個例子當中是虛擬UART的接收FIFO。該訪問被stage 2轉(zhuǎn)換block住,導(dǎo)致一個abort異常被路由到EL2。
- 異常處理程序查詢ESR_EL2關(guān)于異常的信息,如訪問長度,目的寄存器,是load還是store操作。異常處理程序查詢HPFAR_EL2,取得發(fā)生abort的IPA地址。
- Hypervisor通過ESR_EL2和HPFAR_EL2里的相關(guān)信息對相關(guān)虛擬外圍設(shè)備作模擬,模擬完成后通過ERET指令返回vCPU,并從發(fā)生異常的下一條指令繼續(xù)執(zhí)行。
系統(tǒng)內(nèi)存管理單元(System Memory Management Units, SMMUs)
到目前為止,我們只考慮了從處理器發(fā)起的各種訪問。我們還需要考慮其他主設(shè)備如DMA控制器發(fā)起的訪問。我們需要一種方法來擴展stage 2映射以保護這些主設(shè)備的地址空間。如果一個DMA控制器沒有使用虛擬化,那它看起來應(yīng)該如下圖所示:
DMA控制器通常由內(nèi)核驅(qū)動編程控制。內(nèi)核驅(qū)動會確保不違背操作系統(tǒng)層面的內(nèi)存保護原則,即一個應(yīng)用不能使用DMA訪問其沒有權(quán)限訪問的其他應(yīng)用的內(nèi)存。
下面讓我們考慮操作系統(tǒng)運行在虛擬機中的場景。
在這個系統(tǒng)中,Hyperviosr通過stage 2映射來隔離不同VMs的地址空間。這是基于Hypervisor控制的stage 2映射表實現(xiàn)的。而驅(qū)動則直接與DMA控制器交互,這會產(chǎn)生兩個問題:
- 隔離:DMA控制器訪問在虛擬機之間沒有了隔離,這破壞了虛擬機的沙箱功能。地址空間:利用兩級映射轉(zhuǎn)換,使內(nèi)核看到的PAs實際上是IPAs。但DMA控制器看到的仍然是PAs。因此DMA控制器和內(nèi)核看到的是不同的地址空間,為了解決這個問題,每當VM與DMA控制器交互時就需要陷入到Hypervisor中做必要的轉(zhuǎn)換。這種處理方式是極其沒有效率的,且容易出錯。
解決的辦法是將stage 2的機制推廣到DMA控制器。這么做的話,這些主設(shè)備控制器也需要一個MMU,Armv8稱之為SMMU(通常也稱為IOMMU)。
Hypervisor負責配置SMMU,以使DMA控制器看到的物理地址空間與kenrel看到的物理地址空間相同。這樣就能解決上述兩個問題。
指令的陷入與模擬
有時Hypervisor需要模擬一些操作,例如VM里運行的軟件試圖配置處理器的一些屬性,如電源管理或是緩存一致性時。通常你不會允許VM直接配置這些屬性,因為這會打破隔離性,從而影響其他VMs。這就需要通過以陷入的方式產(chǎn)生異常,在異常處理程序中做相應(yīng)的模擬。Armv8包含一些陷入控制來幫助實現(xiàn) 陷入(trapping) – 模擬(emulating)。如果對相應(yīng)操作配置了陷入,則這種操作發(fā)生時會陷入到更高的異常級別,便于Hypervisor模擬。
舉個例子,執(zhí)行等待中斷指令WFI通過會使CPU進入低功耗狀態(tài)。然而,當配置HCR_EL2.TWI==1時,如果在EL0/EL1執(zhí)行WFI則會導(dǎo)致EL2的異常。(注:陷入不是為虛擬化而設(shè)計的,有陷入到EL3和EL1的異常,但異常對虛擬化實現(xiàn)至關(guān)重要。)
對于 WFI的例子里, 操作系統(tǒng)通過在一個idle loop里執(zhí)行 WFI指令,但虛擬機中的操作系統(tǒng)執(zhí)行該指令時,會陷入到Hypervisor里模擬,這時Hypervisor通常會調(diào)度另一個vCPU執(zhí)行。
寄存器的訪問
陷入 – 模擬的另一個用途是用來呈現(xiàn)虛擬寄存器的值。例如寄存器ID_AA64MMFR0_EL1是用來報告處理器內(nèi)存相關(guān)特性的,操作系統(tǒng)可能會讀取該寄存器來決定在內(nèi)核中開啟或關(guān)閉某些特性。Hypervisor可能會給VM呈現(xiàn)一個與實際物理寄存器不同的值。這是怎么實現(xiàn)的呢?首先Hypervisor需要開啟對該寄存器讀操作的陷入。然后,在陷入的異常處理中判斷異常相關(guān)的信息并進行模擬。在如下例子中,就是設(shè)置一個虛擬的值,然后ERET返回。
避免陷入
陷入 – 模擬 的開銷是很大的。這種操作需要先陷入到EL2,然后由Hypervisor做相應(yīng)模擬再返回客戶操作系統(tǒng)。對于某些寄存器如 ID_AA64MMFR0_EL1,操作系統(tǒng)并不經(jīng)常訪問,陷入 – 模擬的開銷還是可以接受的。但對于某些經(jīng)常訪問的寄存器以及性能敏感的代碼,陷入太頻繁會對系統(tǒng)性能造成很大影響。對于這些情況,我們需要盡可能地優(yōu)化 陷入。
- MIDR_EL1: 存有處理器類型信息MPIDR_EL1:親和性配置
Hypervisor可能希望在訪問上述兩個寄存器時不要總是陷入。對這些寄存器,Armv8提供了與其對應(yīng)的不需要陷入的版本。Hypervisor可以在進入VM 時先配置好這些寄存器的值。當VM中讀到 MIDR_EL1 / MPIDR_EL1時會自動返回VPIDR_EL2 / VMPIDR_EL2的值而不發(fā)生陷入。
- VPIDR_EL2:讀取 MIDR_EL1返回 VPIDR_EL2的值避免陷入VMPIDR_EL2:讀取 MPIDR_EL1返回 VMPIDR_EL2的值避免陷入
注意:VPIDR_EL2 / VMPIDR_EL2 在硬件reset后沒有初始化的值,它們必須由軟件啟動代碼初始化一個合理的值。
異常虛擬化
中斷是硬件通知軟件的機制,在一個使用虛擬化的系統(tǒng)中,中斷處理會變得更為復(fù)雜。有些中斷會由Hypervisor直接處理,有些中斷被分配給了VM,需要由VM中的處理程序處理,并且還有可能在接收到這個中斷時,對應(yīng)的VM并沒有被調(diào)度運行。這意味著我們不僅需要支持在EL2中直接處理中斷,還需要一種機制能將收到的中斷轉(zhuǎn)發(fā)給相應(yīng)VM的vCPU。Armv8提供了vIRQs, vFIQs, 和vSErrors來支持虛擬中斷。這些中斷的行為和物理中斷(IRQs, FIQs, 和 SErrors)類似,只不過只有當系統(tǒng)運行在EL0/1是才會收到,運行在EL2/3是收不到虛擬中斷的。
開啟虛擬中斷
虛擬中斷也是根據(jù)中斷類型控制的。為了發(fā)送虛擬中斷到EL0/1, Hypervisor需要設(shè)置 HCR_EL2中相應(yīng)的中斷路由比特位。例如,開啟vIRQ,你需要設(shè)置 HCR_EL2.IMO, 這意味著物理IRQ中斷將被發(fā)送到EL2,同時虛擬中斷將被發(fā)送到EL1。理論上,Armv8可以配置成VM直接接收物理FIQs和虛擬IRQs。但在實際應(yīng)用中,通常配置VM只接收虛擬中斷。
產(chǎn)生虛擬中斷
有兩種方式產(chǎn)生虛擬中斷:
- 配置HCR_EL2,由內(nèi)部CPU核產(chǎn)生使用GICv2及以上版本的外部中斷控制器
我們先來看第一種機制,HCR_EL2中有如下的控制比特位
- VI: 配置vIRQVF: 配置vFIQVSE: 配置vSError
設(shè)置上述比特位等同于中斷控制器向vCPU發(fā)送中斷信號。和常規(guī)物理中斷一樣,虛擬中斷受PSTATE控制。這種機制簡單易用,但有個明顯的缺點,需要由Hypervisor來模擬中斷控制器的相關(guān)操作,一系列的 陷入 – 模擬將帶來性能上的開銷。
第二種方式是使用Arm的通用中斷控制器(Generic Interrupt Controller, GIC)來產(chǎn)生虛擬中斷。從GICv2版本開始,GIC可以通過物理CPU interface 和 虛擬CPU interface發(fā)送物理中斷和虛擬中斷。見下圖:
這兩個CPU interface是等同的,區(qū)別是一個發(fā)送物理中斷信號,另一個發(fā)送虛擬中斷信號。Hypervisor可以將虛擬CPU interface映射給VM,以便VM可以直接和GIC通信。這種方式的好處是Hypervisor只需建立映射,不需要做任何模擬,從而提升了性能。(PS:虛擬化性能提升的關(guān)鍵就在優(yōu)化陷入,減少次數(shù),優(yōu)化流程)
中斷轉(zhuǎn)發(fā)給vCPU的例子
上面介紹了虛擬中斷是如何開啟和生產(chǎn)的。讓我們來看一個中斷轉(zhuǎn)發(fā)給vCPU的例子??紤]一個物理外圍設(shè)備,該設(shè)備被分配給了某個VM,如下圖所示:
具體步驟如下:
- 物理外圍設(shè)備發(fā)送中斷信號給GIC。GIC產(chǎn)生物理中斷異常,可能是IRQ或FIQ。由于配置了HCR_EL2.IMO/FMO,這些異常會被路由到EL2。Hyperviosr發(fā)現(xiàn)該設(shè)備已被分配給了某個VM,于是檢查需要將該中斷信號轉(zhuǎn)發(fā)給哪個vCPU。Hypervisor配置了GIC將該物理中斷以虛擬中斷的形式轉(zhuǎn)給某個vCPU。GIC于是發(fā)送vIRQ/vFIQ信號,如果此時還運行在EL2,這些信號會被忽略。Hypervisor將控制權(quán)返還給vCPU。處理器運行在EL0或EL1,來自GIC的虛擬中斷被接收(受PSTATE控制)。
上面的例子展示了如何將一個物理中斷以虛擬中斷的形式轉(zhuǎn)發(fā)給VM。如果是一個沒有物理中斷對應(yīng)的純虛擬中斷,Hypervisor可以直接注入虛擬中斷。
中斷屏蔽
我們知道中斷屏蔽比特位PSTATE.I, PSTATE.F, PSTATE.A分別對應(yīng)IRQs, FIQs和SErrors。如果運行在虛擬化環(huán)境中,這些比特位的工作方式有些許不同。
例如,對于IRQs,設(shè)置HCR_EL2.IMO意味著
- 物理IRQ路由至EL2對EL0/EL1開啟vIRQs
這同時也改變了PSTATE.I 屏蔽的含義, 當運行在EL0/EL1是,如果 HCR_E2.IMO==1, PSTATE.I針對的是虛擬的vIRQs而非物理的pIRQs。
時鐘虛擬化
Arm體系結(jié)構(gòu)中,每個處理器上都有一組通用時鐘。通用時鐘由一組比較器組成,用來與系統(tǒng)計數(shù)器比較。當比較器的值小于等于系統(tǒng)計數(shù)器時便會產(chǎn)生時鐘中斷。在下圖中,我們可以看到系統(tǒng)中通用時鐘由黃色框部分組成。
下圖展示了虛擬化系統(tǒng)中運行兩個vCPU的時序。
物理世界的時間(墻上時間)4ms里,每個vCPU各運行了2ms。如果我們設(shè)置vCPU0的比較器在T=0之后的3ms產(chǎn)生一個中斷,那么你希望實際在哪個墻上時間點產(chǎn)生中斷呢?是vCPU0的虛擬時間的2ms,也就是墻上時間3ms那個點還是 vCPU0虛擬時間3ms的那個點?
實際上,Arm體系結(jié)構(gòu)同時支持上述兩種設(shè)置,這取決于你使用何種虛擬化方案。讓我們看看這是如何實現(xiàn)的。
運行在vCPU上的軟件可以訪問如下兩種時鐘
- EL1物理時鐘EL1虛擬時鐘
EL1物理時鐘會與系統(tǒng)計數(shù)器模塊直接比較,使用的是絕對的墻上時間。而EL1虛擬時鐘與虛擬計數(shù)器比較。虛擬計數(shù)器是在物理計數(shù)器的基礎(chǔ)上減去一個偏移。Hypervisor負責為當前調(diào)度運行的vCPU指定對應(yīng)的偏移寄存器。這種方式使得虛擬時間只會覆蓋vCPU實際運行的那部分時間。
下圖展示了虛擬時間運作的原理
在一個6ms的時段里,每個vCPU分別運行了3ms。Hypervisor可以使用偏移寄存器來將vCPU的時間調(diào)整為其實際運行的時間。
虛擬化主機擴展(Virtualization Host Extensions, VHE)
圖21顯示了一個Type 1類型的虛擬化系統(tǒng)的軟件棧與異常級別的對應(yīng)關(guān)系,Hypervisor部分運行在EL2,VMs運行在EL0/1。
然而,對于一個Type 2類型的系統(tǒng),其軟件棧與異常級別的對應(yīng)關(guān)系可能如下圖所示:
通常,寄主操作系統(tǒng)的內(nèi)核部分運行在EL1,控制虛擬化的部分運行在EL2。然而,這種設(shè)計有一個明顯的問題。VHE之前的Hypervisor通常需要設(shè)計成high-visor和low-visor兩部分,前者運行在EL1,后者運行在EL2。分層設(shè)計在系統(tǒng)運行時會造成很多不必要的上下文切換,帶來不少設(shè)計上的復(fù)雜性和性能開銷。為了解決這個問題,虛擬化主機擴展 (Virtualization Host Extensions, VHE)應(yīng)運而生。該特性由Armv8.1-A引入,可以讓寄主操作系統(tǒng)的內(nèi)核部分直接運行在EL2上。
將主機操作系統(tǒng)運行在EL2
VHE由系統(tǒng)寄存器 HCR_EL2中的兩個比特位控制
- E2H:VHE使能位TGE:當VHE使能時,控制EL0是Guest還是Host
| Running in | E2H | TGE |
|---------------------------|-----|-----|
|Guest kernel (EL1) | 1 | 0 |
|Guest application (EL0) | 1 | 0 |
|Host kernel (EL2) | 1 | 1* |
|Host application (EL0) | 1 | 1 |
當發(fā)生異常從VM退出到Hypervisor時,TGE將會初始化為0,軟件需要先設(shè)置這一比特,再繼續(xù)運行host kernel的主代碼。
一個典型的配置如下圖:
虛擬地址空間
在VHE引入之前,EL0/1的虛擬地址空間看起來如下。EL0/1分兩塊區(qū)域,上面是內(nèi)核空間,下面是用戶空間。EL2只有一個空間,Hypervisor通常不需要運行應(yīng)用,因此沒有必要劃分內(nèi)核與用戶空間。同理,EL0/1虛擬地址空間支持ASID,但EL2不需要支持。
當VHE引入之后,EL2可以直接運行操作系統(tǒng)代碼。因此需要將地址空間劃分和ASID的支持添加進來。同樣,通過設(shè)置 HCR_EL2.E2H來解決。
當運行在EL0時,HCR_EL2.TGE控制使用EL1還是EL2空間,當應(yīng)用運行在Guest OS (TGE==0)為前者,運行在Host OS(TGE==1)為后者。
重定向寄存器訪問
除了會使用不同的地址空間映射,VHE還有一個問題需要解決,那就寄存器訪問。運行在EL2的內(nèi)核仍然會嘗試訪問*_EL1的寄存器。為了運行無需修改的內(nèi)核,我們需要將EL1的寄存器重定向到EL2。當你設(shè)置E2H后,這一切就會由硬件實現(xiàn)。
但是,重定向又會帶來一個新的問題,那就是Hypervisor完全可能在某些情況下,例如當執(zhí)行任務(wù)切換時, 訪問真正EL1的寄存器。為了解決這個問題,Arm架構(gòu)引入了一種新的別名機制,以_EL12或_EL02結(jié)尾。如下例,就可以在ECH==1的EL2訪問TTBR0_EL1。
異常
通常系統(tǒng)寄存器 HCR_EL2.IMO/FMO/AMO的這幾個比特位可以用來控制物理異常被路由至EL1或EL2。當運行在EL0且TGE==1時,HCR_EL2路由比特將會被忽略,所有物理異常(除了那些由SCR_EL3控制的會被路由至EL3)全部路由到EL2。這是因為Host OS里運行的應(yīng)用是Host OS的一部分,而Host OS運行在EL2。
嵌套虛擬化
Hypervisor可以運行在VM中,這稱之為嵌套虛擬化。
我們將第一個Hypervisor稱為Host Hypervisor,VM中運行的Hypervisor稱為Guest Hypervisor。
在Armv8.3-A之前,Guest Hypervisor可以運行在EL0。但這種設(shè)計需要大量軟件模擬,不僅軟件開發(fā)困難,性能也很差。Armv8.3-A增加了一些新的特性,可以讓Guest Hypervisor運行在EL1。而Armv8.4-A引入的一些新特性,使得這一過程更有效率,雖然仍然需要Host Hypervisor參與做一些額外的工作。
Guest Hypervisor訪問虛擬化控制接口
我們并不希望Guest Hypervisor能直接訪問虛擬化控制接口,因為這么做會破壞VM的沙箱機制,使得虛擬機能夠看到Host平臺的信息。當Guest Hypervisor運行在EL1,并訪問虛擬化控制接口時,HCR_EL2中新的控制比特位可以使這些操作陷入到Host Hypervisor(EL2)以便模擬。
- HCR_EL2.NV:開啟硬件輔助嵌套虛擬化HCR_EL2.NV1:開啟額外需要陷入的操作HCR_EL2.NV2:開啟重定向到內(nèi)存VNCR_EL2:當NV2==1時,指向一個內(nèi)存中的結(jié)構(gòu)體
Armv8.3-A添加了NV和NV1控制比特。在此之前,從EL1訪問*_EL2寄存器時的行為是未定義的,通常是會產(chǎn)生一個EL1的異常。而控制比特NV和NV1使得這種訪問可以被陷入到EL2。這就使得Guest Hypervisor可以運行在EL1,同時由運行在EL2的Host Hypervisor來模擬這些操作。NV還會導(dǎo)致EL1運行ERET陷入到EL2。
下圖展示了Guest Hypervisor如何創(chuàng)建并啟動虛擬機:
- 從EL1訪問*_EL2寄存器將導(dǎo)致Guest Hypervisor陷入到EL2。Host Hypervisor記錄Guest Hypervisor創(chuàng)建的相關(guān)配置。Guest Hypervisor嘗試進入其創(chuàng)建的虛擬機,此時ERET指令會陷入到EL2。Host Hypervisor根據(jù)Guest Hypervisor的配置,設(shè)置相關(guān)寄存器以便啟動VM,清理掉NV比特位,最后進入Guest Hypervisor創(chuàng)建的Guest運行。
按上述的方法, 在Guest Hypervisor訪問任何一個*_EL2寄存器時都會發(fā)生陷入。切換操作如 任務(wù)切換,vCPU切換,VMs切換都會訪問大量寄存器,每次陷入都會導(dǎo)致異常的進入與返回,從而帶來嚴重的 陷入 – 模擬性能問題。(回憶前面的內(nèi)容, 虛擬化性能提升的關(guān)鍵就在優(yōu)化陷入,減少次數(shù),優(yōu)化流程)。Armv8.4-A提供了一個更好的方案,當NV2被設(shè)置時,從EL1訪問*_EL2寄存器將會被重定向到一塊內(nèi)存區(qū)域。Guest Hypervisor可以多次讀寫這塊寄存器區(qū)域而不發(fā)生陷入。只有當最后運行ERET時,才會陷入到EL2。而后,Host Hypervisor可以從該內(nèi)存區(qū)域中提取相關(guān)配置并代Guest Hypervisor執(zhí)行相關(guān)操作。
- 從EL1訪問*_EL2寄存器將會被重定向到一塊內(nèi)存區(qū)域,該內(nèi)存區(qū)域的地址由Host Hypervisor在 VNCR_EL2中指定。Guest Hypervisor嘗試進入其創(chuàng)建的虛擬機,此時ERET指令會陷入到EL2Host Hypervisor從內(nèi)存中提取配置信息,設(shè)置相關(guān)寄存器,以便啟動VM,清理掉NV比特位,最后進入Guest Hypervisor創(chuàng)建的Guest運行。
這個改進方法相比之前的方法會減少陷入到Host Hypervisor的次數(shù),從而提升了性能。
安全世界虛擬化
虛擬化擴展最早是在Armv7-A引入的。在Armv7-A中的Hyp模式等同于AArch32中的EL2,僅僅在非安全世界才存在。作為一個可選特性,Armv8.4-A增加了安全世界下EL2的支持。支持安全世界EL2的處理器,需配置EL3下的SCR_EL3.EEL2比特位來開啟這一特性。設(shè)置了這一比特位,才允許使用安全狀態(tài)下的虛擬化功能。
在安全世界虛擬化之前,EL3通常用于運行安全狀態(tài)切換軟件和平臺固件。然而從設(shè)計上來說,我們希望EL3中運行的軟件越少越好,因為越簡單才會更安全。安全狀態(tài)虛擬化使得我們可以將平臺固件移到EL1中運行,由虛擬化來隔離平臺固件和可信操作系統(tǒng)內(nèi)核。下圖展示了這一理念:
安全EL2與兩個IPA空間
Arm體系結(jié)構(gòu)定義了安全世界和非安全世界兩個物理地址空間。在非安全狀態(tài)下,stage 1轉(zhuǎn)換的的輸出總是非安全的,因此只需要一個IPA空間來給stage 2使用。然而,對于安全世界,stage 1的輸出可能是安全的也能是非安全的。Stage 1轉(zhuǎn)換表中的NS比特位控制使用安全地址還是非安全地址。這意味著在安全世界,需要兩個IPA地址空間。
與stage 1表不同,stage 2轉(zhuǎn)換表中沒有NS比特位。因為對于一個特定的IPA空間,要么全都是安全地址,要么全都是非安全的,因此只需要由一個寄存器比特位來確定IPA空間。通常來說,非安全地址經(jīng)過stage 2轉(zhuǎn)換仍然是非安全地址,安全地址經(jīng)過stage 2轉(zhuǎn)換仍然是安全地址。
虛擬化的損耗
虛擬化的損耗主要在于虛擬機和Hypervisor切換需要保存和恢復(fù)寄存器。Armv8系統(tǒng)中,最少需要對如下寄存器做處理
- 31 x 64-bit通用寄存器(x0…x30)32 x 128-bit浮點/SIMD寄存器(V0…V31)兩個棧寄存器(SP_EL0, SP_EL1)
使用LDP和STP指令,Hypervisor需要運行33條指令來存儲和恢復(fù)這些寄存器。虛擬化最終的損耗不僅取決于硬件還取決于Hypervisor的設(shè)計。