內存虛擬化

現代多任務操作系統設計,一般進程之間使用不同的虛擬地址空間相互隔離, 在實現上:

  • 操作系統負責維護進程頁表,映射虛擬地址到物理地址的關係;
  • CPU的內存管理單元(MMU)負責執行地址轉換;
  • CPU提供TLB(Translation lookaside buffer)緩存最近用到的轉換結果,加速轉換效率;

虛擬化技術引入後,內存地址空間更加複雜了,客戶機(Guest)和宿主機(Host)都有自己的地址空間:

  • GVA: Guest虛擬地址
  • GPA: Guest物理地址
  • HVA: Host虛擬地址
  • HPA: Host物理地址

顯而易見,Guest負責GVA和GPA之間的轉換;Host負責HVA和HPA之間的轉換;而GPA和HPA之間的轉換,就需要虛擬化層(Hypervisor)輔助了,這個過程一般被稱為內存虛擬化。

影子頁表

早期的X86 CPU硬體輔助虛擬化能力很不完善,所以Hypervisor需要通過軟體實現內存虛擬化。因此,Hypervisor為每個客戶機每套頁表額外再維護一套頁表,通常也稱為影子頁表。

同時, Hypervisor截獲客戶機裡面任何試圖修改客戶機頁表或者刷新TLB的操作,將GVA到GPA的的修改,轉變成GVA到GPA的修改。這些操作包括:

  • 寫gCR3(Guest CR3)寄存器和原來一樣的內容,一般用作刷新TLB;
  • 寫gCR3(Guest CR3)寄存器不同的物理地址,一般是發生了進程切換;
  • 修改部分頁表,這時候必須調用INVLPG指令失效對應的TLB;

這樣,Guest中的頁表實際變成了虛擬頁表,Hypervisor截獲了Guest相關的修改操作並更新到影子頁表,而真正裝入物理MMU是影子頁表; Guest中GVA和GPA之間的轉換實際上變成了GVA與HPA的轉換,TLB中緩存的也是GVA和HPA的映射,Guest內存訪問沒有額外的地址轉換開銷。

當然,影子頁表也帶來了下面的主要缺點:

  1. Hypervisor 需要為每個客戶機的每個進程的頁表都要維護一套相應的影子頁表,這會帶來較大內存上的額外開銷;
  2. 客戶在讀寫CR3、執行INVLPG指令或客戶頁表不完整等情況下均會導致VM exit,這導致了內存虛擬化效率很低;
  3. 客戶機頁表和和影子頁表的同步也比較複雜;

Intel EPT技術

為了簡化內存虛擬化的實現,以及提升內存虛擬化的性能,Intel推出了EPT(Enhanced Page Table)技術,即在原有的頁表基礎上新增了EPT頁表實現另一次映射。這樣,GVA-GPA-HPA兩次地址轉換都由CPU硬體自動完成。

通過EPT的GVA和HPA大概翻譯的過程:

  1. 處於非根模式的CPU載入guest進程的gCR3;
  2. gCR3是GPA,cpu需要通過查詢EPT頁表來實現GPA->HPA;
  3. 如果沒有,CPU觸發EPT Violation, 由Hypervisor截獲處理;
  4. 假設客戶機有m級頁表,宿主機EPT有n級,在TLB均miss的最壞情況下,會產生MxN次內存訪問,完成一次客戶機的地址翻譯;

總結

為了解決GVA-GPA-HPA的轉換關係,在沒有硬體輔助的時代,Hypervisor通過影子頁表,很巧妙的將GVA-GPA映射到GVA-HPA, 功能雖然達成,但是在很多實際場景下,如進程頻繁切換,內存頻繁分配釋放等,性能損耗會非常大;

EPT在硬體的幫助下,實現內存虛擬化簡單直接,傳統頁表繼續負責GVA-GPA, 而EPT負責GPA-HPA; 雖然內存訪問延時可能會增加一些,但是大幅減少了因為頁表更新帶來的vmexit, 綜合性價比提升巨大, 所以現代內存虛擬化,基本都被EPT統一了。

推薦閱讀:

相关文章