1. 前言
ARM架構(gòu)為了支持虛擬化做了些擴(kuò)展,稱為虛擬化擴(kuò)展(Virtualization Extensions)。原先為VT-x創(chuàng)建的KVM(Linux-based Kernel Virtual Machine)適配了ARM體系結(jié)構(gòu),引入了KVM/ARM (the Linux ARM hypervisor)。KVM/ARM沒有在hypervisor中引入復(fù)雜的核心功能,而是利用了Linux內(nèi)核中現(xiàn)有的功能進(jìn)行適配。KVM/ARM與Linux集成,在可移植性和硬件支持方面會有所收益。其實也可以開發(fā)獨立的bare metal hypervisor,這樣可能會有更好地性能,但這種方法在ARM上不太好使。
在下面,我們將描述KVM/ARM的設(shè)計如何使得它可以從現(xiàn)有kernel的集成中獲益,同時利用硬件虛擬化特性。
2. Split-mode虛擬化
KVM/ARM利用了現(xiàn)有的kernel功能,例如調(diào)度器(scheduler),但KVM/ARM如果直接在EL2中運行Linux kernel,會存在兩個問題。
第一,linux中與底層體系結(jié)構(gòu)相關(guān)的代碼是為在kernel模式下工作而編寫的,不經(jīng)過修改就不能在EL2中運行,因為EL2是一個完全不同于普通kernel模式的CPU模式。Linux kernel社區(qū)不太可能接受在EL2中運行kernel所需的重大更改。更重要的是,為了保持與沒有EL2的硬件的兼容性并將Linux作為Guest OS運行,必須編寫低級代碼以在兩種模式下工作,這可能會導(dǎo)致緩慢而復(fù)雜的代碼路徑。舉個簡單的例子,頁表錯誤處理程序需要獲取導(dǎo)致頁表錯誤的虛擬地址。在EL2中,這個地址存儲在與kernel模式不同的寄存器中。
第二,在EL2中運行整個內(nèi)核會對本機(jī)性能產(chǎn)生不利影響。例如,EL2有自己獨立的地址空間,但是kernel模式使用兩個頁表基本寄存器(TTBR)在user地址空間和kernel地址空間之間提供熟悉的3GB/1GB分割,而EL2使用單個頁表寄存器,因此不能直接訪問地址空間的user空間部分。經(jīng)常使用的訪問user內(nèi)存的函數(shù)需要kernel顯式地將用戶空間數(shù)據(jù)映射到kernel地址空間,然后執(zhí)行必要的拆解和TLB維護(hù)操作,從而導(dǎo)致ARM的性能不好。
綜上所述,KVM/ARM引入了split-mode虛擬化,這種一種新的hypervisor設(shè)計方法,它將核心hypervisor分開,以便它可以跨不同的特權(quán)CPU模式運行,從而利用每種CPU模式提供的特定優(yōu)勢和功能。KVM/ARM使用split-mode虛擬化來利用EL2支持的ARM硬件虛擬化,同時利用在kernel模式下運行的現(xiàn)有Linux kernel服務(wù)。Split-mode虛擬化允許KVM/ARM與Linux kernel集成,而無需對現(xiàn)有代碼庫進(jìn)行重大修改。
這是通過將hypervisor分成兩個組件來實現(xiàn)的:lowvisor和highvisor,如圖1所示。
圖1 KVM/ARM系統(tǒng)架構(gòu)
Lowvisor的設(shè)計是利用EL2中提供的硬件虛擬化支持來提供三個關(guān)鍵功能。首先,lowvisor通過適當(dāng)?shù)挠布渲脕碓O(shè)置正確的執(zhí)行上下文,并在不同的執(zhí)行上下文之間實施保護(hù)和隔離。Lowvisor直接與硬件保護(hù)功能交互,因此非常關(guān)鍵,且代碼庫需要保持絕對最小。其次,lowvisor從VM執(zhí)行上下文切到到host執(zhí)行上下文,反之亦然。Host執(zhí)行上下文用于運行hypervisor和host Linux kernel。我們將執(zhí)行上下文稱為一個世界,將從一個世界切換到另一個世界稱為世界切換(world switch),因為系統(tǒng)的整個狀態(tài)都發(fā)生了變化。由于lowvisor是唯一在EL2中運行的組件,因此只有它可以負(fù)責(zé)執(zhí)行世界切換所需的硬件重新配置。第三,lowvisor提供了一個虛擬化Trap處理程序,它處理必須Trap到hypervisor的中斷和異常。所有發(fā)送到hypervisor的Trap必須首先發(fā)送到lowvisor。在世界切換到highvisor完成后,lowvisor只執(zhí)行所需的最少量的處理,并將要完成的大部分工作推遲到highvisor。
Highvisor作為host linux kernel的一部分在kernel模式下運行。因此,它可以直接利用現(xiàn)有的linux功能,例如調(diào)度器,并且可以利用標(biāo)準(zhǔn)的kernel軟件數(shù)據(jù)結(jié)構(gòu)和機(jī)制來實現(xiàn)其功能,例如locking機(jī)制和memory分配功能。它使高層功能更容易在highvisor中實現(xiàn)。例如,雖然lowvisor提供低級Trap處理程序和低級機(jī)制來從一個世界切換到另一個世界,但highvisor處理來自VM和的stage-2頁表錯誤并執(zhí)行指令仿真。請注意,部分VM以kernel模式運行,就像highvisor一樣,但是啟用了stage-2轉(zhuǎn)換和Trap到EL2。
因為hypervisor在kernel模式和EL2之間是分開的,所以在VM和highvisor之間的切換涉及到多個模式轉(zhuǎn)換。在運行VM時,向highvisor發(fā)出Trap將首先向EL2中l(wèi)owvisor發(fā)出Trap。然后lowvisor將發(fā)起另一個Trap去運行highvisor。類似地,從highvisor切換到VM需要從kernel模式切換到EL2,然后切換到VM。因此,在切換到或切換出highvisor時,split-mode虛擬化會產(chǎn)生雙重Trap成本。在ARM上,執(zhí)行這些模式轉(zhuǎn)換到EL2或從EL2轉(zhuǎn)換的唯一方法就是通過Trap。不過,這個額外的Trap在ARM上并不是一個顯著的性能成本。
KVM/ARM使用內(nèi)存映射接口根據(jù)需要在highvisor和lowvisor之間共享數(shù)據(jù)。由于內(nèi)存管理可能很復(fù)雜,它利用了linux中現(xiàn)有內(nèi)存管理子系統(tǒng)的功能來管理highvisor和lowvisor的內(nèi)存。但是管理lowvisor的內(nèi)存涉及到額外的挑戰(zhàn),它需要管理EL2的單獨地址空間。一種簡單的方法是重用host kernel的頁表,并在EL2中使用它們來使地址空間相同。不過這樣有問題,因為EL2使用和kernel模式不同的頁表格式。因此,highvisor顯式地管理EL2頁表,將在EL2中執(zhí)行的任何代碼以及highvisor和lowvisor之間共享的任何數(shù)據(jù)結(jié)構(gòu)映射到EL2和kernel模式中的相同虛擬地址。
3. CPU虛擬化
為了虛擬化CPU,KVM/ARM必須向VM提供一個接口,該接口本質(zhì)上與底層的實際硬件相同,同時確保hypervisor仍然控制硬件。它包括確保在虛擬機(jī)中運行的軟件必須與在物理CPU上運行的軟件具有對相同寄存器狀態(tài)的一致訪問,以及確保切換VM運行中,與hypervisor及其host kernel相關(guān)聯(lián)的物理硬件狀態(tài)是一致的。為了不影響VM隔離,寄存器狀態(tài)可以簡單地通過保存VM狀態(tài)和從VM切換到host時從內(nèi)存恢復(fù)主機(jī)狀態(tài)進(jìn)行上下文切換,反之亦然。KVM/ARM將對所有其它敏感狀態(tài)的訪問配置為Trap到EL2,因此hypervisor可以模擬它。
表1為在kernel模式和user模式下運行的軟件可以看到的寄存器狀態(tài),以及KVM/ARM對每個寄存器組的虛擬化方法。Lowvisor有自己專用的配置寄存器,僅供EL2使用。只要硬件支持,在世界切換期間,KVM/ARM上下文會切換寄存器,因為它允許VM直接訪問硬件。例如,VM可以直接對stage-1頁表基本寄存器進(jìn)行編程,而不需要Trap到hypervisor,這在大多數(shù)Guest OS中是相當(dāng)常見的操作。KVM/ARM在敏感指令和訪問硬件狀態(tài)時執(zhí)行Trap和模擬,這些狀態(tài)可能會影響hypervisor或?qū)⒂嘘P(guān)硬件的信息泄漏給VM,違反了虛擬化原理。例如。如果VM執(zhí)行導(dǎo)致CPU斷電的WFI指令,KVM/ARM會Trap,這樣的操作應(yīng)該只由hypervisor執(zhí)行,以保持對硬件的控制。
表1 VM和Host的狀態(tài)
在kernel或user模式下運行虛擬機(jī)與在kernel或user模式下運行hypervisor之間的區(qū)別取決于EL2在世界切換期間如何配置virtualization extensions。從host切換到VM的過程如下:
將所有host GP寄存器存到EL2的堆棧中;
給VM配置vGIC;
給VM配置timers;
將所有host特定的配置寄存器存到EL2堆棧中;
將VM的配置寄存器加載到硬件,這個操作不會影響當(dāng)前執(zhí)行,因為EL2使用它自己的配置寄存器,這些寄存器從host狀態(tài)中分離出來。
配置EL2以Trap浮點操作(VFP寄存器的lazy context switching),trap中斷,trap CPU halt指令(WFI/WFE),trap SMC指令,Trap特定配置寄存器訪問和trao調(diào)試寄存器訪問;
將虛擬機(jī)特定的ID寫入shadow ID寄存器中,這由ARM虛擬化擴(kuò)展定義,并由虛擬機(jī)訪問,以代替ID寄存器中的硬件值;
設(shè)置stage-2頁表基寄存器(VTTBR)并啟用stage-2地址轉(zhuǎn)換;
恢復(fù)所有g(shù)uest GP寄存器;
進(jìn)入user模式或kernel模式;
CPU將留在虛擬機(jī)世界,直到有event發(fā)生,觸發(fā)進(jìn)入EL2的trap。這樣的事件可能由上面提到的任何Trap、stage-2頁表錯誤或硬件中斷引起。由于event需要highvisor的服務(wù),要么模擬虛擬機(jī)預(yù)期的硬件行為,要么服務(wù)于設(shè)備中斷,KVM/ARM必須執(zhí)行另一個世界切換到highvisor和它的host。這需要先到lowvisor,然后再到highvisor。從VM世界切換到host世界需要以下步驟:
保存所有VM GP寄存器;
關(guān)閉stage-2轉(zhuǎn)換;
配置EL2不Trap任何寄存器訪問和指令;
保存所有VM特定的配置寄存器;
加載host的配置寄存器到硬件;
配置host的timers;
保存VM特定的vGIC狀態(tài);
恢復(fù)所有host GP寄存器
進(jìn)入kernel模式;
4. 內(nèi)存虛擬化
如圖2所示,ARM提供了stage-2頁表來將Guest物理地址轉(zhuǎn)換為host物理地址。KVM/ARM通過啟用stage-2轉(zhuǎn)換,為在虛擬機(jī)中運行時為所有內(nèi)存訪問提供內(nèi)存虛擬化。Stage-2轉(zhuǎn)換只能在EL2中配置,它的使用對VM是完全透明的。Highvisor管理stage-2翻譯頁表,只允許訪問專門為VM分配的內(nèi)存。其它訪問將導(dǎo)致stage-2頁表錯誤,從而Trap到hypervisor。該機(jī)制確保虛擬機(jī)不能訪問hypervisor或其它虛擬機(jī)的內(nèi)存,包括任何敏感數(shù)據(jù)。在highvisor和lowvisor中運行時禁用stage-2轉(zhuǎn)換,因為highvisor完全控制整個系統(tǒng)并直接管理host物理地址。當(dāng)hypervisor切換到VM時,它啟用stage-2轉(zhuǎn)換并相應(yīng)地配置stage-2頁表基寄存器。盡管highvisor和VM共享相同的CPU模式,但stage-2轉(zhuǎn)換確保highvisor不受VM的任何訪問。
圖2 ARM的兩級頁表
KVM/ARM是一個split-mode虛擬化來利用現(xiàn)有的kernel內(nèi)存分配、頁表引用計數(shù)和頁表操作代碼。KVM/ARM通過考慮出錯的gPA來處理stage-2頁表出錯,以及該地址是否屬于VM內(nèi)存映射,KVM/ARM通過簡單地調(diào)用一個現(xiàn)有的kernel函數(shù)(如get_user_pages)為VM分配一個頁表,并將分配的頁表映射到stage-2頁表中的VM。
5. I/O虛擬化
KVM/ARM利用現(xiàn)有的QEMU和Virtio user空間設(shè)備模擬來提供I/O虛擬化。在硬件層面上,ARM架構(gòu)上的所有I/O機(jī)制都是基于對MMIO設(shè)備區(qū)域的load/store操作。除了直接分配給VM的設(shè)備,所有硬件MMIO區(qū)域不能被虛擬機(jī)訪問。KVM/ARM使用stage-2轉(zhuǎn)換來確保不能從虛擬機(jī)直接訪問物理設(shè)備。在為VM分配的RAM區(qū)域之外的任何訪問都將被hypervisor Trap,hypervisor可以根據(jù)fault地址將訪問路由到QEMU中的特定模擬設(shè)備。
6. 中斷虛擬化
KVM/ARM利用其與Linux的集成來重用現(xiàn)有的設(shè)備驅(qū)動程序和相關(guān)功能,包括處理中斷。當(dāng)在虛擬機(jī)中運行時,KVM/ARM配置CPU將所有硬件中斷Trap到EL2。在每個中斷中,它執(zhí)行到hypervisor的世界切換,host處理中斷,這樣hypervisor就可以保留完全控制硬件資源。當(dāng)在host和highvisor中運行時,中斷直接Trap到kernel模式,避免通過EL2的開銷。在這兩種情況下,所有硬件中斷處理都是通過重用Linux現(xiàn)有的中斷處理功能在host中完成的。
但是,虛擬機(jī)必須以虛擬中斷的形式接收來自模擬設(shè)備的通知,multicore Guest OSs必須能夠從一個虛擬core發(fā)送虛擬IPIs到另一個虛擬xore。KVM/ARM通過vGIC向虛擬機(jī)注入虛擬中斷,減少EL2的Trap數(shù)量。通過編程vGIC hypervisor CPU控制接口中的list registers,虛擬中斷被提升到虛擬CPU。KVM/ARM配置stage-2頁表,來防止虛擬機(jī)訪問控制接口,只允許訪問vGIC虛擬CPU接口,保證只有hypervisor可以編程控制接口,虛擬機(jī)可以直接訪問vGIC虛擬接口。然而,Guest OS仍然會嘗試訪問GIC分發(fā)器來配置GIC,并講IPI從一個虛擬core發(fā)送到另一個虛擬core。這樣的訪問將Trap到hypervisor,hypervisor必須模擬分發(fā)程序。
KVM/ARM引入了虛擬分發(fā)器,這是GIC分發(fā)器的一個軟件模型,作為highvisor的一部分。虛擬分發(fā)器向User空間公開接口,因此user空間中的模擬設(shè)備可以向虛擬分發(fā)器發(fā)起虛擬中斷,并向VM公開與物理GIC分發(fā)器相同的MMIO接口。虛擬分發(fā)器保存關(guān)于每個中斷狀態(tài)的內(nèi)部軟件狀態(tài),并在調(diào)度VM時使用此狀態(tài),編程list registers以注入虛擬中斷。例如,如果虛擬CPU0向虛擬CPU1發(fā)送一個IPI,分發(fā)程序?qū)⒕幊烫摂MCPU1的list registers以引發(fā)一個虛擬IPI中斷給虛擬CPU1。
理想情況下,虛擬分發(fā)程序只在必要時訪問硬件list registers,因為設(shè)備MMIO操作通常比cache內(nèi)存訪問慢得多。當(dāng)調(diào)度不同的VM在物理core上運行時,需要list registers的完整上下文切換,但當(dāng)簡單地在VM和hypervisor之間切換時,則不一定需要上下文切換。例如,如果沒有掛起的虛擬中斷,則不需要訪問任何list register。注意,一旦hypervisor在切換到VM時將一個虛擬中斷寫入一個list register,那么在切換會hypervisor時,它還必須讀回這個list register,因為list register描述了虛擬中斷的狀態(tài)。KVM/ARM的初始未優(yōu)化版本使用了一種簡單的方法,該方法完全切換所有vGIC狀態(tài),包括每個世界開關(guān)上的list register。
7. 計時器虛擬化
讀取計數(shù)器和編程計時器是許多操作系統(tǒng)中用于進(jìn)程調(diào)度和定期輪詢設(shè)備狀態(tài)的常見操作。例如,Linux讀取一個計數(shù)器來確定進(jìn)程是否已經(jīng)過期,并編程計數(shù)器來確保進(jìn)程沒有超出其允許的時間片段。由于各種原因,應(yīng)用程序工作負(fù)載也經(jīng)常使用計時器。為每個這樣的操作Trap到hypervisor可能會導(dǎo)致明顯的性能開銷,并且允許VM直接訪問計時硬件通常意味著放棄對硬件資源的定時控制,因為VM可以禁用計時器并在較長的時間內(nèi)控制CPU。
KVM/ARM利用ARM的通用定時器的硬件虛擬化特性來允許虛擬機(jī)直接訪問可讀計數(shù)器和編程計時器,而不會引起Trap到EL2,同時確保hypervisor仍然控制硬件。由于使用EL2控制對物理計時器的訪問,因此任何控制EL2模式的軟件都可以訪問物理計時器。KVM/ARM通過使用hypervisor中的物理計時器和不允許從虛擬機(jī)訪問物理計時器來維護(hù)硬件控制。作為Guest OS運行的Linux kernel只能訪問虛擬計時器,因此可以直接訪問計時器硬件,而不會被hypervisor Trap住。
不過由于體系結(jié)構(gòu)的限制,虛擬計時器不能直接引發(fā)虛擬中斷,而總是引發(fā)硬件中斷,而硬件中斷會Trap到hypervisor。KVM/ARM檢測虛擬機(jī)的虛擬計時器是否到期,并向虛擬機(jī)注入相應(yīng)的虛擬中斷,在highvisor中執(zhí)行所有硬件ACK和EOI操作。硬件只為每個物理CPU提供一個虛擬計時器,多個虛擬CPU可以再這個硬件實例上復(fù)用。為了在這種情況下支持虛擬計時器,KVM/ARM在虛擬機(jī)進(jìn)入hypervisor時檢測未過期的計時器,并利用現(xiàn)有的OS功能在虛擬計時器本來觸發(fā)的時候編程一個軟件計時器,讓虛擬機(jī)處于運行狀態(tài)。當(dāng)這樣的軟件計時器觸發(fā)時,將執(zhí)行一個回調(diào)函數(shù),該函數(shù)使用上面描述的虛擬分發(fā)器向VM引發(fā)一個虛擬計時器中斷。
8. ARM體系結(jié)構(gòu)的改進(jìn)
根據(jù)KVM/ARM開發(fā)人員的經(jīng)驗,對ARM架構(gòu)進(jìn)行了一系列改進(jìn),以避免對KVM/ARM等type-2 hypervisor進(jìn)行split-mode虛擬化的需要。這些改進(jìn)包括Virtualization Host Extensions(VHE),它現(xiàn)在是ARM 64-bit架構(gòu)新版本ARMv8.1的一部分。VHE允許將設(shè)計在EL1中運行的OS運行在EL2中,而無需對OS源代碼進(jìn)行實質(zhì)性修改。
VHE是通過添加一個新的控制位E2H來提供的,該位在系統(tǒng)啟動時安裝使用VHE的type-2 hypervisor是設(shè)置的。如果該位沒有設(shè)置,ARMv8.1在硬件虛擬化方面與ARMv8相同,保留了與現(xiàn)有hypervisor的向后兼容性。如果該位設(shè)置了,那么VHE將開啟三個主要特性。
首先,VHE擴(kuò)展了EL2,向CPU添加額外的物理寄存器狀態(tài),這樣EL1中可用的任何寄存器和功能也可以在EL2中使用。例如,EL1有兩個寄存器:TTBR0_EL1和TTBR1_EL1。第一個用于查找頁表中較低VA范圍內(nèi)的虛擬地址,第二個用于較高VA范圍內(nèi)的虛擬地址。它提供了一種在user空間和kernel空間之間分隔虛擬空間的方便、高效地方法。但是,如果沒有VHE,EL2只有一個頁表基寄存器TTBR0_EL2,這使得在EL2中運行時支持EL1的分隔VA空間存在問題。對于VHE,EL2將有第二個頁表基寄存器TTBR1_EL2,從而可以支持分隔VA空間EL2的方式與EL1相同。用于啟用與host集成的type-2 hypervisor操作系統(tǒng)去支持在EL2種分割VA空間,這是運行EL2種的host OS所必需的,以便管理user空間和kernel之間的VA空間。
其次,VHE提供了一種機(jī)制來透明地訪問額外的EL2寄存器狀態(tài)。簡單地提供額外的EL2寄存器不足以在EL2中運行未修改的OS,因為現(xiàn)有的操作系統(tǒng)被寫入訪問EL1寄存器。例如,Linux使用了TTBR1_EL1,不影響EL2種運行的translation系統(tǒng)。提供額外的寄存器TTBR1_EL2仍然需要修改Linux,以便分別在EL2中運行時使用TTBR1_EL2而不是TTBR1_EL1。為了避免迫使OS供應(yīng)商給軟件增加這種額外的復(fù)雜性,VHE允許未經(jīng)修改的軟件在EL2種執(zhí)行,并使用EL1寄存器訪問函數(shù)指令編碼透明的訪問EL2寄存器。例如,當(dāng)前的操作系統(tǒng)軟件用指令MRS x1,TTBR1_EL1讀取TTBR1_EL1寄存器。對于VHE,軟件仍然執(zhí)行相同的指令,但是硬件實際訪問的是TTBR1_EL2寄存器。只要設(shè)置了E2H bit,訪問EL1寄存器實際上訪問了EL2寄存器,從而透明地重寫了對EL2的寄存器訪問。添加了一組新的特殊指令來訪問EL2中EL1寄存器,hypervisor可以使用這些指令在將EL1中運行的VM之間進(jìn)行切換。例如,如果hypervisor希望訪問Guest的TTBR1_EL1,它將使用指令MRS x1,TTBR_EL21。
第三,VHE擴(kuò)展了EL2的memory translation能力。在ARMv8種,EL2和EL1使用不同的頁表格式,因此編寫在EL1種運行的軟件必須經(jīng)過修改才能在EL2中運行。在ARMv8.1中,當(dāng)設(shè)置了E2H位時,EL2頁表格式現(xiàn)在與EL1格式兼容。因此,以前在EL1中運行的OS現(xiàn)在可以在EL2中運行而無需修改,因為它可以使用相同的EL1頁表格式。
圖3顯示type-1和type-2 hypervisor程序如何映射到具有VHE的體系結(jié)構(gòu)。
圖3 虛擬化擴(kuò)展(VHE)
Type-1 hypervisor(如Xen)可以明確地設(shè)計為在EL2中運行,而無序任何額外的支持,它不設(shè)置VHE引入的E2H位,并且EL2的行為與ARMv8完全相同。Type-2 hypervisor(如KVM/ARM)在系統(tǒng)啟動時設(shè)置了E2H位,host OS kernel只在EL2中運行,不會在EL1中運行。Type-2 hypervisor kernel可以在EL2中不加修改地運行,因為VHE為每個EL1寄存器提供了一個等效的EL2寄存器,并透明地將EL1寄存器訪問從EL2重寫為EL2寄存器訪問,而且EL1和EL2之間的頁表格式現(xiàn)在是兼容的。從host user空間到host kernel的轉(zhuǎn)換直接從EL0到EL2進(jìn)行,例如處理系統(tǒng)調(diào)用,如圖3的箭頭所示。從VM到hypervisor的轉(zhuǎn)換現(xiàn)在無須上下文切換EL1狀態(tài),因為hypervisor不使用EL1。