0.寫在前面

    我一直以為現在的硬件輔助虛擬化技術不支持嵌套虛擬化,即物理機上只能有一層虛擬機,虛擬機裏面不能再創建虛擬機。但是最近接觸個事情,感覺這個知識點需要刷新。     虛擬機裏面一直跑容器,但是容器存在隔離性的安全問題,業界提出來的安全容器方案為給容器加一個linux內核,例如Kata Containers 和 Firecracker 這類安全容器,這樣不就是虛擬機裏面再跑虛擬機了嗎?也就是安全容器用到了嵌套虛擬化。     現在最新的技術已經解決了嵌套虛擬化的問題了。來看看怎麼實現的吧。

1.虛擬化需要解決什麼問題

參考計算虛擬化基礎知識(kvm+qemu)https://blog.51cto.com/u_17355821/14245330

這篇文章裏提了兩個重要的問題,在嵌套虛擬化裏面同樣需要解決: (1)CPU虛擬化:CPU特權指令(敏感指令)的虛擬化 (2)內存虛擬化:地址在頁表中如何轉換 那麼這兩個問題在嵌套虛擬化中如何解決呢?

    先講一下嵌套虛擬化的層級關係:     L0 (Level 0): 物理主機及其 Hypervisor(如 ESXi、Hyper-V、KVM)。     L1 (Level 1): 在第一層 Hypervisor 上運行的虛擬機(Guest VM)。這台虛擬機“認為”自己是一台物理服務器。     L2 (Level 2): 在 L1 虛擬機內部,由 Kata 或 Firecracker 啓動的 輕量級虛擬機(MicroVM),容器進程就運行在其中。

2.嵌套虛擬化中的CPU特權指令處理

2.1 基本邏輯

    特權指令的處理:虛擬的VMExit     在非嵌套環境中,當Guest OS執行一條特權指令(如CPUID),會直接導致一次VMExit,控制權從虛擬機交還給宿主的Hypervisor。<br>     在嵌套環境中,當L2 Guest OS執行特權指令時,會發生以下一系列精密的“接力”操作:     L0的首次接管:硬件會首先產生一次VMExit。但此時,L0 Hypervisor發現這個退出事件實際上是由一個運行在L1 Guest VM中的Hypervisor(即L1 Hypervisor)所管理的L2虛擬機觸發的。     注入虛擬中斷:L0 Hypervisor並不會自己處理這個請求,而是將這個退出事件(例如,一條CPUID指令的執行)包裝成一個虛擬中斷,注入到L1 Guest VM中。     L1的接管處理:在L1 Guest VM看來,這個虛擬中斷就像一次真實的VMExit。於是,其內部的L1 Hypervisor被喚醒,它像在物理機上一樣,接管並模擬執行這條CPUID指令,然後將結果返回給L2 Guest OS。

2.2 常見問題

2.2.1 L2 guestos執行cpuid特權指令,硬件產生vmexit,問題是L0 hypervisor是如何知道這個cpuid是l2 guest os產生的而不是l1guestos 產生的

    當 CPU 在執行 L2 的代碼時,當前活躍的 VMCS 指向的是 L2 的 VMCS。此時,如果 L2 中的代碼執行了一條特權指令(如 CPUID),CPU 的虛擬化硬件會立即觸發一次 VM-Exit。     關鍵在於這次退出的上下文信息。VM-Exit 發生時,硬件不僅會通知 L0 Hypervisor 發生了退出,還會在當前的 VMCS(即 L2 的 VMCS)中記錄詳細的退出原因、指令信息等。因此,L0 Hypervisor 在接管控制權時,通過檢查這些信息,就能明確知道:這次退出不是發生在 L1 虛擬機上下文​中的,所以L0就直接把信息提供給L1讓L1處理。

3.嵌套虛擬化中的內存地址轉換

3.1 轉換過程的詳細分解

第一級轉換:L2虛擬地址 -> L2物理地址 轉換執行者:L2虛擬機內部的頁表(由L2 Guest OS管理)。 轉換機制:當L2虛擬機內的應用程序訪問一個虛擬地址時,CPU的MMU會像在物理機上一樣,使用L2的CR3寄存器指向的頁表來遍歷翻譯。這個階段完全在L2虛擬機內部完成,L0 Hypervisor不干預。 轉換結果:得到L2認為的“物理地址”,即GPA2。<br> 第二級轉換:L2物理地址 -> L1物理地址 轉換執行者:L1 Hypervisor為L2虛擬機設置的擴展頁表。 轉換機制:GPA2並不是真正的物理地址,它需要被再次轉換。這個轉換由硬件自動完成。CPU的MMU在完成第一級轉換得到GPA2後,會立刻使用L1 Hypervisor為L2配置的EPT,將GPA2翻譯為L1物理地址(GPA1)。 關鍵點:這次轉換由硬件直接處理,通常不需要L0 Hypervisor的介入,因此效率很高。<br> 第三級轉換:L1物理地址 -> L0物理地址 轉換執行者:L0 Hypervisor為L1虛擬機設置的擴展頁表。 轉換機制:GPA1同樣不是最終地址,它需要被再次轉換。CPU的MMU會再次利用EPT機制,這次是使用L0 Hypervisor為L1配置的EPT,將GPA1翻譯為最終的宿主機物理地址(HPA)。

3.2 常見問題

3.2.1 MMU什麼時候使用EPT繼續轉換,什麼時候只用CR3

    虛擬機監控器(Hypervisor)在為每個虛擬機(VM)創建虛擬 CPU 時,會初始化一個名為 VMCS​ 的數據結構,它相當於這個 vCPU 的“配置文件”。其中一個至關重要的控制字段是 “Enable EPT”,虛擬機的VMCS中Enable EPT=1。     當 L0 Hypervisor 為 L1 Guest 啓動一個 vCPU 時,如果設置了 “Enable EPT = 1”,就意味着告訴硬件:“這個虛擬機擁有物理內存的幻覺,它看到的‘物理地址’需要你通過 EPT 再轉換一次才能得到真正的主機物理地址。” 這便開啓了兩級轉換的基礎。     在嵌套虛擬化中,當 L1 Guest 內部也運行一個 Hypervisor 去啓動 L2 Guest 時,L0 Hypervisor 會通過一種叫 “VMCS Shadowing”​ 的技術,將 L1 Hypervisor 希望為 L2 Guest 設置的 EPT 指針也納入管理。這時,硬件會知道,當前正在處理的是 L2 Guest 的內存訪問。

3.2.2 MMU是怎麼知道執行一次EPT轉換(普通虛擬化)還是兩次EPT轉換(嵌套虛擬化)

    這裏就要理解EPT指針(位於其VMCS中)的作用了。     EPT指針:EPT翻譯的機制不是隻執行一次,而是類似於一個循環。虛擬機在創建時,L0在其VMCS的EPT指針中放的是EPT指針鏈表,包括其所有祖先虛擬機以及L0 Hypervisor的EPT表。EPT翻譯機制會按照EPT指針鏈表進行循環轉換,一直轉換到最終的物理地址上