本文首發於我的公眾號 Linux雲計算網路(id: cloud_dev),號內有 10T 書籍和視頻資源,後台回復「1024」即可領取,歡迎大家關注,第一時間掌握技術乾貨!

虛擬內存

我們知道,早期的計算機內存,只有物理內存,而且空間是極其有限的,每個應用或進程在使用內存時都得小心翼翼,不能覆蓋別的進程的內存區。

為了避免這些問題,就提出了虛擬內存的概念,其抽象了物理內存,相當於對物理內存進行了虛擬化,保證每個進程都被賦予一塊連續的,超大的(根據系統結構來定,32 位系統定址空間為 2^32,64 位系統為 2^64)虛擬內存空間,進程可以毫無顧忌地使用內存,不用擔心申請內存會和別的進程衝突,因為底層有機制幫忙處理這種衝突,能夠將虛擬地址根據一個頁表映射成相應的物理地址。

這種機制正是虛擬化軟體做的事,也就是 MMU 內存管理單元。

本文要說的不是這種虛擬內存,而是基於虛擬機的內存虛擬化,它們本質上是一樣的,通過對虛擬內存的理解,再去理解內存虛擬化就比較容易了。

結合前面的文章,我們知道,虛擬化分為軟體虛擬化和硬體虛擬化,而且遵循 intercept 和 virtualize 的規律。

內存虛擬化也分為基於軟體的內存虛擬化和硬體輔助的內存虛擬化,其中,常用的基於軟體的內存虛擬化技術為「影子頁表」技術,硬體輔助內存虛擬化技術為 Intel 的 EPT(Extend Page Table,擴展頁表)技術。

為了講清楚這兩門技術,我們從簡易到複雜,循序漸進,逐步揭開其神秘面紗。

常規軟體內存虛擬化

虛擬機本質上是 Host 機上的一個進程,按理說應該可以使用 Host 機的虛擬地址空間,但由於在虛擬化模式下,虛擬機處於非 Root 模式,無法直接訪問 Root 模式下的 Host 機上的內存。

這個時候就需要 VMM 的介入,VMM 需要 intercept (截獲)虛擬機的內存訪問指令,然後 virtualize(模擬)Host 上的內存,相當於 VMM 在虛擬機的虛擬地址空間和 Host 機的虛擬地址空間中間增加了一層,即虛擬機的物理地址空間,也可以看作是 Qemu 的虛擬地址空間(稍微有點繞,但記住一點,虛擬機是由 Qemu 模擬生成的就比較清楚了)。

所以,內存軟體虛擬化的目標就是要將虛擬機的虛擬地址(Guest Virtual Address, GVA)轉化為 Host 的物理地址(Host Physical Address, HPA),中間要經過虛擬機的物理地址(Guest Physical Address, GPA)和 Host 虛擬地址(Host Virtual Address)的轉化,即:

GVA -> GPA -> HVA -> HPA

其中前兩步由虛擬機的系統頁表完成,中間兩步由 VMM 定義的映射表(由數據結構 kvm_memory_slot 記錄)完成,它可以將連續的虛擬機物理地址映射成非連續的 Host 機虛擬地址,後面兩步則由 Host 機的系統頁表完成。如下圖所示。

這樣做得目的有兩個:

  1. 提供給虛擬機一個從零開始的連續的物理內存空間。

  2. 在各虛擬機之間有效隔離、調度以及共享內存資源。

影子頁表技術

接上圖,我們可以看到,傳統的內存虛擬化方式,虛擬機的每次內存訪問都需要 VMM 介入,並由軟體進行多次地址轉換,其效率是非常低的。因此才有了影子頁表技術和 EPT 技術。

影子頁表簡化了地址轉換的過程,實現了 Guest 虛擬地址空間到 Host 物理地址空間的直接映射。

要實現這樣的映射,必須為 Guest 的系統頁表設計一套對應的影子頁表,然後將影子頁表裝入 Host 的 MMU 中,這樣當 Guest 訪問 Host 內存時,就可以根據 MMU 中的影子頁表映射關係,完成 GVA 到 HPA 的直接映射。而維護這套影子頁表的工作則由 VMM 來完成。

由於 Guest 中的每個進程都有自己的虛擬地址空間,這就意味著 VMM 要為 Guest 中的每個進程頁表都維護一套對應的影子頁表,當 Guest 進程訪問內存時,才將該進程的影子頁表裝入 Host 的 MMU 中,完成地址轉換。

我們也看到,這種方式雖然減少了地址轉換的次數,但本質上還是純軟體實現的,效率還是不高,而且 VMM 承擔了太多影子頁表的維護工作,設計不好。

為了改善這個問題,就提出了基於硬體的內存虛擬化方式,將這些繁瑣的工作都交給硬體來完成,從而大大提高了效率。

EPT 技術

這方面 Intel 和 AMD 走在了最前面,Intel 的 EPT 和 AMD 的 NPT 是硬體輔助內存虛擬化的代表,兩者在原理上類似,本文重點介紹一下 EPT 技術。

如下圖是 EPT 的基本原理圖示,EPT 在原有 CR3 頁表地址映射的基礎上,引入了 EPT 頁表來實現另一層映射,這樣,GVA->GPA->HPA 的兩次地址轉換都由硬體來完成。

這裡舉一個小例子來說明整個地址轉換的過程。假設現在 Guest 中某個進程需要訪問內存,CPU 首先會訪問 Guest 中的 CR3 頁表來完成 GVA 到 GPA 的轉換,如果 GPA 不為空,則 CPU 接著通過 EPT 頁表來實現 GPA 到 HPA 的轉換(實際上,CPU 會首先查看硬體 EPT TLB 或者緩存,如果沒有對應的轉換,才會進一步查看 EPT 頁表),如果 HPA 為空呢,則 CPU 會拋出 EPT Violation 異常由 VMM 來處理。

如果 GPA 地址為空,即缺頁,則 CPU 產生缺頁異常,注意,這裡,如果是軟體實現的方式,則會產生 VM-exit,但是硬體實現方式,並不會發生 VM-exit,而是按照一般的缺頁中斷處理,這種情況下,也就是交給 Guest 內核的中斷處理程序處理。

在中斷處理程序中會產生 EXIT_REASON_EPT_VIOLATION,Guest 退出,VMM 截獲到該異常後,分配物理地址並建立 GVA 到 HPA 的映射,並保存到 EPT 中,這樣在下次訪問的時候就可以完成從 GVA 到 HPA 的轉換了。

總結

內存虛擬化經歷從虛擬內存,到傳統軟體輔助虛擬化,影子頁表,再到硬體輔助虛擬化,EPT 技術的進化,效率越來越高。


公眾號後台回復「加群」,帶你進入高手如雲交流群

我的公眾號 Linux雲計算網路(id: cloud_dev) ,號內有 10T 書籍和視頻資源,後台回復 「1024」 即可領取,分享的內容包括但不限於 Linux、網路、雲計算虛擬化、容器Docker、OpenStack、Kubernetes、工具、SDN、OVS、DPDK、Go、Python、C/C++編程技術等內容,歡迎大家關注。


推薦閱讀:
相关文章