ARM平台的虛擬化介紹


本篇博文主要介紹虛擬化的基本思想以及在arm平台如何做虛擬化,arm提供的硬件feature等等。

虛擬化技術簡介

虛擬化技術

虛擬化是一個概念,單從這個概念的角度來看,只要是用某一種物品去模擬另一種物品都可以稱為虛擬化,甚至於有些飯店用豆腐做出肉的味道,我認為這也可以稱為一種虛擬化。但是這里我們主要討論的是計算機領域的虛擬化,我們這樣定義虛擬化“虛擬化是將單一物理設備模擬為相互隔離的多個虛擬設備,同時保證這些虛擬設備的高效性”。這個概念的定義里還包含了對虛擬化的要求,也就是這里的隔離性(isolated)和有效性(efficient)。我們常說的hypervisor,有些書也把它稱為VMM(virtual machine monitor)則是一個直接運行在物理硬件上的軟件,它的功能就是管理物理硬件,以便在不同的虛擬機之間共享這些物理資源(cpu,內存,外設等等),既然hypervisor直接給物理外設打交道,那它當然需要運行在特權模式了,在過去沒有virtualization extesion的情況下,guest os和guest application只能都運行在de-privileged模式,如下圖所示。

Popek和Goldberg有一篇虛擬化的經典論文,把需要在特權模式下執行的指令分成了兩類:

  • sensitive instructions(敏感指令):這些指令試圖去更改系統資源的配置信息,或者它的執行結果依賴於系統的狀態。
  • privileged instructions(特權指令):這些指令在非特權模式下會trap(產生異常,陷入中斷向量表),在特權模式下可以正常執行。

Popek和Goldberg提出構建hypervisor的要求:敏感指令是特權指令的子集。這種標准現在被稱為classically virtualized(經典可虛擬化模型),雖然在不滿足這個要求的情況下也可以做虛擬化(二進制翻譯技術,后面會介紹),但是如果滿足這個要求,實現起來會容易很多。下面介紹現有的虛擬化技術:

  • Pure virtualization(完全虛擬化):完全虛擬化要求硬件架構是可虛擬化的(符合經典可虛擬化模型),當trap進入hypervisor后,由hypervisor去模擬敏感指令的執行,這項技術也被稱為trap-and-emulate。當一個guest os想要去訪問特權資源(物理外設),就會產生一個trap喚醒hypervisor,hypervisor去模擬這個訪問,然后返回到guest os的下一條指令去繼續執行。如下圖所示,紅色箭頭表示一個trap。可以看出,每一條特權指令都需要很多條指令去仿真,所以這種trap-and-emulate開銷非常大,對系統性能有很大影響。

  • Binary rewriting(二進制重寫):二進制重寫就是當硬件架構不可虛擬化(不符合經典可虛擬化模型)采用的方法。它可以分為靜態的和動態的,靜態的二進制重寫是通過掃描ELF文件,把所有的敏感指令替換成一個trap指令(系統調用指令),或者用一些非敏感指令去仿真執行這條敏感指令,動態的二進制重寫對敏感指令的處理和靜態的類似,只不過它是在運行時去逐條分析指令,其實這種方法更不好,因為不管是不是敏感指令,都需要逐條分析才能確定,非常耗時,靜態方法在運行時的性能要好過動態方法,但是經常出現一些莫名其妙的錯誤,因為運行時狀態非常復雜,靜態修改很難預料到所有情況。上述過程如下圖所示。

  • para-virtualization(類虛擬化):這種虛擬化方法,很多書都把它譯為半虛擬化,其實這種譯法是不准確的,半虛擬化(partial-virtualization)是一個早已存在的技術,它只虛擬化部分外設來滿足某些專門的軟件的執行環境,但是不能運行所有可能運行在物理機上的軟件。如果讀者對此有疑問,請參閱《系統虛擬化:原理與實現》,intel開源技術中心和復旦大學並行處理研究所著,書中1.3節對此有討論。其實想解釋清楚這部分內容是很難的,還要理解各種各樣的虛擬化漏洞。簡單來說,類虛擬化通過修改guest os的源碼(API級),使得guest os避免這些難以虛擬化的指令(虛擬化漏洞)。操作系統通常會使用到處理器提供的全部功能,例如特權級別、地址空間和控制寄存器等。類虛擬化首先要解決的問題就是如何陷入VMM。典型的做法是修改guest os的相關代碼,讓os主動讓出特權級別,而運行在次一級特權上。這樣,當guest os試圖去執行特權指令時,保護異常被觸發,從而提供截獲點供VMM來模擬(也可以采用hypercall的方式,下面介紹)。既然內核代碼已經需要修改,類虛擬化還可以進一步優化I/O。也就是說,類虛擬化不是去模擬真實世界中的設備,因為太多的寄存器模擬會降低性能。相反,類虛擬化可以自定義出高度優化的I/O協議,這種I/O協議完全基於事物,可以達到近乎物理機的速度。
    其實OKL4用的類虛擬化就是修改hypervisor提供給guest os的API(不同於底層硬件),同時修改guest os的源碼,把那些敏感指令換成hypercall(calls into hypervisor)。下圖展示的是,對於pure virtualization,硬件和hypervisor的API是相同的,但是對於para-virtualization是不同的。

虛擬化技術的比較

Pure virtualization and binary rewriting

pure virtualization和binary rewriting都是不修改機器的API的,所以任何guest os都可以直接運行在虛擬化環境。但是,由於所有的特權指令都會導致trap,所以在虛擬環境下特權指令的執行開銷要遠遠高於在native環境下。以前,x86和ARM都不符合classically virtualized時,VMWare采用binary rewriting在x86架構上實現虛擬化,經過優化后的性能開銷小於10%,但是這項技術十分復雜。由於實現起來的復雜,就會增加運行在特權模式下的代碼,這會增加attack surface和hypervisor出現bug的幾率,所以會降低整個系統的安全性和隔離性。

Para-virtualization

Para-virtualization雖然是一個新詞,在2002年中的Denali virtual machine monitor被提出來。但是這種設計理念早在1970的IBM的CMS系統就出現了,當時使用DIAG指令調用到hypervisor里去,並且一直到現在還有很多研究機構在使用這種理念,如Mach,Xen和L4。
Para-virtualization相比於pure virtualization可以提供更好的性能,因為它直接使用各種API而不是通過trap->decode->hardware emulation的過程來實現仿真。當然,它的缺點我也在之前的博客中提到過,那就是必須修改源碼,讓guest os使用新的API,這不僅是一項繁重的任務,同時對於一些非開源的操作系統,我們必須采用其他方法,除非這些非開源操作系統的廠商願意與我們合作。

Virtual memory in virtualization environment

為什么要把內存管理部分單獨拿出來討論一下呢?因為這部分很復雜,其實之前我們討論的內容主要都是cpu運行的問題,比如各種指令和各種模式之間的切換。關於內存,我們先討論沒有引入guest os時的虛擬內存管理,然后再討論引入guest os之后的變化。
虛擬內存管理涉及的內容很多,這里不討論各種內存分配算法,如何降低缺頁率等等,只分析虛擬地址如何轉換成物理地址。我們知道,ARM架構是通過MMU+TLB來完成從VA(virtual address,虛擬地址)到PA(physical address,物理地址)的轉換,對於頁表的訪問實際上是由硬件自動完成的(如果不缺頁的話)。但是加入了虛擬化之后,這個轉換就復雜了,guest page table不完成從va到pa的轉換,只是負責從guest va到guest pa的轉換,而由hypervisor完成由guest pa到實際物理地址的轉換,這個轉換過程如下圖所示。

這個圖表現的很清晰,但是想實現是非常難的,因為只有一個頁表基址寄存器,所以硬件無法識別是從guest va到guest pa的轉換還是va到pa的轉換,在沒有硬件支持的情況下,只能通過影子頁表才能實現,影子頁表的原理也是把兩步轉換(guest va->guest pa->pa)轉換為一步,中間的同步用hash來做。影子頁表在構建的時候,每次對guest page table的訪問都需要trap,由hypervisor把guest pa轉換成實際物理地址。如果讀者想了解這一塊內容,我建議深入學習一下KVM以前關於影子頁表的實現(由於x86的硬件支持,目前KVM已經放棄影子頁表),這里我們沒辦法深入探討影子頁表,但了解它大致是怎么一回事兒之后,我們可以分析以下它的性能。首先,它的性能一定非常不好,因為每次對guest page table的訪問都需要trap,而且每次guest page table的修改還需要同步到影子頁表上面,雖然用hash的方式能提速,但是相比於native環境性能差距比較大(NOVA做過一個實驗,光訪問頁表的性能損失大約是23%),而且實現起來非常復雜。Intel和ARM對這一部分都提供了硬件支持,由硬件來完成這里提到的兩級頁表轉換。其實根據程序運行時的局部性原理,如果每次訪問都能TLB hit的話,這種二級頁表轉換和一級頁表轉換差別不大,但是當TLB miss的時候,需要訪問two stage的頁表訪問的性能還是差別比較大的,盡管這部分由硬件來做。舉個例子,比如KVM在Linux-64位的情況下,是4級頁表轉換,從va到pa需要訪問5次頁表,那么引入two stage之后,就需要5*5=25次訪問頁表,讀者可以思考一下這里為什么是相乘的關系。

ARM介紹

我們首先介紹ARM架構里的各個部分,介紹它們的目的是為了理解當arm引入virtualization extension之后對它們的影響。

ARM總體介紹

arm是一種精簡指令集(reduced instruction set computer,RISC)架構,精簡(reduced)的意思是每條匯編指令獨自完成所有的工作,而與之相對的復雜指令集則不是,它的一條匯編指令可能會翻譯成好幾條機器指令。大部分精簡指令集的指令都在單個時鍾周期內完成,它采用一種讀取和存儲分開的架構(load-store architecture),數據處理指令和I/O指令是分開的,數據處理指令是操作一個寄存器的值,和復雜指令集不同,關於復雜指令集的對應操作讀者請自行查閱資料。現在arm已經推出v8架構,關於v8架構我還不太熟,所以這里以v7作為介紹(后續有時間我會研究下v8,在這里進行補充)。v7架構包含16個32bit的通用寄存器,還有一些寄存器是和特定的處理器模式相關的,還有各種協處理器的寄存器,這些寄存器將會在后面展開敘述。

ARM協處理器介紹

ARM協處理器是ARM架構的重要擴展,ARM架構允許最多16個協處理器,其中cp15被保留完成各種控制。cp15作用非常強大,它控制整個系統配置,cache和TLB的管理,MMU的控制和系統性能監控,我們這里主要討論cp15的內存管理功能。
當cpu想要訪問一個虛擬地址的時候,它首先去TLB里面查這個虛擬地址和ASID,都匹配的話就可以直接返回物理地址,cpu通過物理地址去訪問物理內存就行了。如果TLB miss,MMU就會去訪問頁表,然后找到這個虛擬地址對應的物理地址,把這個虛擬地址、當前進程的PID、物理地址加入到TLB中去,然后返回物理地址給cpu去訪問內存。這里面涉及的很多細節這里不討論了,建議讀者去參看ARM官方介紹。

處理器模式和TrustZone

ARM v7包含8種處理器模式(在v8已經變成4種exception level了,從EL0到El3),其中包含1種非特權模式和7種特權模式:

  • 特權模式:FIQ、IRQ、supervisor、monitor、abort、undefined、system;
  • 非特權模式:user。

顯然,除了應用程序運行在user模式以外,其他全部運行在特權模式。ARM的virtualization extension需要處理器支持TrustZone extension,我們來看一下TrustZone是什么。TrustZone將處理器的執行狀態分為兩個世界:

  • secure world:用於運行可信軟件;
  • non-secure world:用於運行不可信軟件。

這里的兩個世界和處理器模式是重疊的,軟件可以在任何模式、任何世界上運行。那么secure world和non-secure world的區別在哪呢?這里的secure又從何而來?是這樣的,secure world有自己獨有的內存和外設,這部分內容只有運行在secure world的軟件可以訪問,運行在non-secure world的軟件是不可以訪問的。這里引入了一個新的處理器模式,monitor mode,它運行在secure world,被用於做雙系統(secure and non-secure world)之間的切換,如下圖所示。

我們可以基於TrustZone去做虛擬化,因為它能夠隔離內存、中斷並且確保non-secure world的特權軟件也不可能訪問或者修改運行在secure world的軟件的配置信息。然后這樣做的缺陷是,在non-secure world只能運行一個guest os,在secure world運行一個hypervisor。Green Hill的INTEGRITY就是這樣做的,感興趣的讀者可以去Google一下。

中斷控制器

GIC(generic interrupt controller)是ARM里的中斷控制器,現在也已經支持virtualization extension。GIC可以分為兩部分:

  • Distributor(分發器):分發器負責接收中斷,設置這個中斷是否enable和它的優先級,之后把它送到對應的cpu interface上去。
  • CPU interface(中斷接口):這部分負責屏蔽低優先級中斷(相對於正在處理的中斷的優先級),讓高優先級的中斷搶占cpu。

當外設產生中斷的時候,這個中斷首先發送給Distributor,Distributor將這個中斷發送給對應的cpu interface。當cpu interface接受到這個中斷的時候,它會檢查這個中斷是否enable,如果enable再去比較這個中斷的優先級和當前正在處理的中斷的優先級,進而決定處理器是否立即處理這個中斷。

Virtualization Issues with the ARM Architecture

標准的ARM架構是不符合可虛擬化模型的,有很多敏感指令在非特權模式下執行卻不會產生trap。比如CPS指令,這條指令的作用是改變處理器狀態,當這條指令在用戶態執行時不會產生trap,甚至沒有任何效果,可以認為是簡單的跳過。即使所有的敏感指令都會產生trap,在ARM架構上用上述的trap-and-emulate技術也是很困難的,因為ARM的敏感指令非常多,只要和特權資源交互的指令都是敏感指令,比如虛擬內存子系統,中斷控制子系統和協處理器,用上述方式的話開銷太大,對系統性能有很大沖擊。比如,arm-v7架構不支持頁表訪問的虛擬化,那么就需要影子頁表,每次訪問guest pa都需要trap,同樣地,中斷控制器也需要被仿真,當中斷很頻繁的時候(timer tick),這種仿真的開銷也是非常大的,為了克服這種種弊端,ARM推出了virtualization extension。

ARM對虛擬化的硬件支持

在討論arm新增加的virtualization extension之前,我們知道對硬件虛擬化的支持主要有intel的VT-x和AMD的AMD-V,它們兩個十分類似,所以這里我們只介紹VT-x,看看它對虛擬化做了怎樣的支持(為后面做對比)。

  • 將cpu的模式分為hypervisor(VMX root operation)和guest(VMX non-root operation)。
  • 可以配置一些敏感指令和事件,讓它們產生或者不產生trap。
  • (新增)提供擴展頁表(EPT,extended page table),通過這個頁表在硬件上完成second-stage of translation,其實就是常說的二級頁表翻譯。
  • (新增)在TLB上新增加了VM tag去標識每一個虛擬機,這樣可以避免每次VM-entry和VM-exit時的TLB flush操作(其實還增加了VPID,去標識VM里虛擬進程的進程id)。
  • (新增)在Intel的 VT-d里增加了對DMA操作的支持,而且是一種安全的DMA(具體怎么實現的安全讀者可以自己分析下)。

接下來我們看看ARM對虛擬化的支持,這里討論的虛擬化支持主要是針對v7架構,並且需要實現上文提到的TrustZone。利用硬件擴展實現pure virtualization的總體架構如下圖所示:

  • hypervisor運行在non-secure world里的hyp mode,這個hyp mode使hypervisor可以管理non-secure world里其他所有的模式(user mode和kernel mode)里運行的軟件。
  • guest os運行在non-secure world的特權模式(kernel mode),guest application運行在non-secure world的user mode。

上述內容是對虛擬化擴展的一個總體介紹,具體來說,ARM新增了以下幾個feature:

  • hyp mode:hyp mode是運行在non-secure world的最高特權級模式,如上圖所示。它負責管理guest os,hypervisor運行在這個新的模式里。這個模式將hypervisor和運行中的guest os分開,guest os運行在non-secure的kernel mode。
  • Second-stage of translation:由hypervisor負責把所有的gust pa轉換成實際物理地址,其實就是從物理上支持兩級頁表轉換,而不需要使用影子頁表。
  • 中斷控制:這部分后面展開敘述。
  • 仿真支持:當trap進hypervisor時,硬件向hypervisor提供一些額外的信息,消除了hypervisor取指令然后decode的開銷。因為對外設的模擬需要采用trap-and-emulate技術,削減這項技術的開銷可以有效的提升性能。
  • trap配置:不是所有的敏感指令或特權操作都需要trap進hypervisor進行處理,我們可以配置指令或操作是否trap,這樣可以減少不必要的trap,從而減少開銷,提升性能。

讀者可以自行對比Intel和ARM對虛擬化支持的相同點和不同點,分析他們為什么這樣做。接下來展開敘述上文提到的中斷控制部分。

中斷控制

ARM創建了一個新的硬件模塊,virtual CPU interface,類比我們前面在介紹GIC時提到過的CPU interface,這個硬件模塊可以直接被map到guest os里,從而避免使用trap-and-emulate去仿真CPU interface,guest os可以直接操作這個virtual CPU interface,例如開、關中斷。當然關於GIC的另一個部分,Distributor,我們仍然需要通過trap-and-emulate去仿真,但是它對性能的影響不大,因為它只是在初始化的時候負責enable中斷,之后就不再修改了。
當中斷到來時,所有的中斷都首先被送到hypervisor里進行處理,由hypervisor通過virtual CPU interface發送給當前正在執行的guest os。虛擬中斷可以和物理中斷進行映射,這樣guest os就可以直接操作物理中斷而不需要通過hypervisor了。下圖所示是一個中斷處理流程:

  1. 外設產生一個中斷發送到Distributor
  2. Distributor把這個中斷發送給CPU interface
  3. CPU interface告訴hypervisor去處理這個中斷
  4. hypervisor對這個中斷進行檢查,發現這個中斷是送給guest os處理的,它會設置一個虛擬中斷,將物理中斷和虛擬中斷連接在一起,把這個虛擬中斷加入到virtual CPU interface。
  5. virtual CPU interface會根據hypervisor加入的虛擬中斷向guest os發送一個中斷
  6. guest os通過virtual CPU interface發來的中斷進行處理,處理之后返回
  7. virtual CPU interface發現這個虛擬中斷來自於一個物理中斷,就會在Dirtributor上清除這個物理中斷(表示處理完畢),整個虛擬中斷處理過程結束。

這里面還涉及一個硬件擴展,論文上把它稱為“priority drop”。正常情況下,當一個中斷正在處理的時候,低優先級的中斷是不能夠搶占處理器的,但是在虛擬化環境卻不是這樣,比方說有兩個guest os,我們暫且稱之為os1和os2,假設os1正在處理一個高優先級中斷,這時又有一個中斷是給os2處理的,這個中斷的優先級低於os1的中斷的優先級,但是它們應該互不影響才對。ARM加入這個硬件擴展就是為了處理上述問題,每個guest os都有自己的優先級屏蔽策略,互不影響。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM