[原] KVM 虛擬化原理探究(4)— 內存虛擬化


KVM 虛擬化原理探究(4)— 內存虛擬化

標簽(空格分隔): KVM


內存虛擬化簡介

前一章介紹了CPU虛擬化的內容,這一章介紹一下KVM的內存虛擬化原理。可以說內存是除了CPU外最重要的組件,Guest最終使用的還是宿主機的內存,所以內存虛擬化其實就是關於如何做Guest到宿主機物理內存之間的各種地址轉換,如何轉換會讓轉換效率更高呢,KVM經歷了三代的內存虛擬化技術,大大加快了內存的訪問速率。

傳統的地址轉換

在保護模式下,普通的應用進程使用的都是自己的虛擬地址空間,一個64位的機器上的每一個進程都可以訪問0到2^64的地址范圍,實際上內存並沒有這么多,也不會給你這么多。對於進程而言,他擁有所有的內存,對內核而言,只分配了一小段內存給進程,待進程需要更多的進程的時候再分配給進程。
通常應用進程所使用的內存叫做虛擬地址,而內核所使用的是物理內存。內核負責為每個進程維護虛擬地址到物理內存的轉換關系映射。
首先,邏輯地址需要轉換為線性地址,然后由線性地址轉換為物理地址。

邏輯地址 ==> 線性地址 ==> 物理地址

邏輯地址和線性地址之間通過簡單的偏移來完成。
image_1aq931k0p42710me1il511r1kin9.png-59.9kB

一個完整的邏輯地址 = [段選擇符:段內偏移地址],查找GDT或者LDT(通過寄存器gdtr,ldtr)找到描述符,通過段選擇符(selector)前13位在段描述符做index,找到Base地址,Base+offset就是線性地址。

為什么要這么做?據說是Intel為了保證兼容性。

邏輯地址到線性地址的轉換在虛擬化中沒有太多的需要介紹的,這一層不存在實際的虛擬化操作,和傳統方式一樣,最重要的是線性地址到物理地址這一層的轉換。

傳統的線性地址到物理地址的轉換由CPU的頁式內存管理,頁式內存管理。
頁式內存管理負責將線性地址轉換到物理地址,一個線性地址被分五段描述,第一段為基地址,通過與當前CR3寄存器(CR3寄存器每個進程有一個,線程共享,當發生進程切換的時候,CR3被載入到對應的寄存器中,這也是各個進程的內存隔離的基礎)做運算,得到頁表的地址index,通過四次運算,最終得到一個大小為4K的頁(有可能更大,比如設置了hugepages以后)。整個過程都是CPU完成,進程不需要參與其中,如果在查詢中發現頁已經存在,直接返回物理地址,如果頁不存在,那么將產生一個缺頁中斷,內核負責處理缺頁中斷,並把頁加載到頁表中,中斷返回后,CPU獲取到頁地址后繼續進行運算。

image_1aq93m25p10v913qns5kpsfbjm.png-152.1kB

KVM中的內存結構

由於qemu-kvm進程在宿主機上作為一個普通進程,那對於Guest而言,需要的轉換過程就是這樣。

  Guest虛擬內存地址(GVA)
          |
    Guest線性地址 
          |
   Guest物理地址(GPA)
          |             Guest
   ------------------
          |             HV
    HV虛擬地址(HVA)
          |
      HV線性地址
          |
    HV物理地址(HPA)

What's the fu*k ?這么多...
別着急,Guest虛擬地址到HV線性地址之間的轉換和HV虛擬地址到線性地址的轉換過程可以省略,這樣看起來就更清晰一點。

  Guest虛擬內存地址(GVA)
          |
   Guest物理地址(GPA)
          |             Guest
  ------------------
          |             HV
    HV虛擬地址(HVA)
          |
    HV物理地址(HPA)

前面也說到KVM通過不斷的改進轉換過程,讓KVM的內存虛擬化更加的高效,我們從最初的軟件虛擬化的方式介紹。

軟件虛擬化方式實現

第一層轉換,由GVA->GPA的轉換和傳統的轉換關系一樣,通過查找CR3然后進行頁表查詢,找到對應的GPA,GPA到HVA的關系由qemu-kvm負責維護,我們在第二章KVM啟動過程的demo里面就有介紹到怎樣給KVM映射內存,通過mmap的方式把HV的內存映射給Guest。

image_1aq96r87sq6v12lp1r8119n0cd21t.png-37.4kB

struct kvm_userspace_memory_region region = {
    .slot = 0,
    .guest_phys_addr = 0x1000,
    .memory_size = 0x1000,
    .userspace_addr = (uint64_t)mem,
};

可以看到,qemu-kvm的kvm_userspace_memory_region結構體描述了guest的物理地址起始位置和內存大小,然后描述了Guest的物理內存在HV的映射userspace_addr,通過多個slot,可以把不連續的HV的虛擬地址空間映射給Guest的連續的物理地址空間。

image_1aq965a4v18e1mnj15pb3du13pu1g.png-41.7kB

軟件模擬的虛擬化方式由qemu-kvm來負責維護GPA->HVA的轉換,然后再經過一次HVA->HPA的方式,從過程上來看,這樣的訪問是很低效的,特別是在當GVA到GPA轉換時候產生缺頁中斷,這時候產生一個異常Guest退出,HV捕獲異常后計算出物理地址(分配新的內存給Guest),然后重新Entry。這個過程會可能導致頻繁的Guest退出,且轉換過程過長。於是KVM使用了一種叫做影子頁表的技術。

影子頁表的虛擬化方式

影子頁表的出現,就是為了減少地址轉換帶來的開銷,直接把GVA轉換到HVP的技術。在軟件虛擬化的內存轉換中,GVA到GPA的轉換通過查詢CR3寄存器來完成,CR3保存了Guest中的頁表基地址,然后載入MMU來做地址轉換。
在加入了影子頁表的技術后,當訪問到CR3寄存器的時候(可能是由於Guest進程后導致的),KVM捕獲到這個操作,CPU虛擬化章節 EXIT_REASON_CR_ACCESS,qemu-kvm通過載入特俗的CR3和影子頁表來欺騙Guest這個就是真實的CR3,后面的操作就和傳統的訪問內存的方式一致,當需要訪問物理內存的時候,只會經過一層的影子頁表的轉換。

image_1aq972tgu2tr15g216kh1u3s1s1q2a.png-47.2kB

影子頁表由qemu-kvm進程維護,實際上就是一個Guest的頁表到宿主機頁表的映射,每一級的頁表的hash值對應到qemu-kvm中影子頁表的一個目錄。在初次GVA->HPA的轉換時候,影子頁表沒有建立,此時Guest產生缺頁中斷,和傳統的轉換過程一樣,經過兩次轉換(VA->PA),然后影子頁表記錄GVA->GPA->HVA->HPA。這樣產生GVA->GPA的直接關系,保存到影子頁表中。

image_1aq97hvkm14lg14al112i1j1m1ren2n.png-19.4kB

影子頁表的引入,減少了GVA->HPA的轉換過程,但是壞處在於qemu-kvm需要為Guest的每個進程維護一個影子頁表,這將帶來很大的內存開銷,同時影子頁表的建立是很耗時的,如果Guest進程過多,將導致頻繁的影子頁表的導入與導出,雖然用了cache技術,但是還是軟件層面的,效率並不是最好,所以Intel和AMD在此基礎上提供了硬件虛擬化技術。

EPT硬件加速的虛擬化方式

image_1aq997mlo1rt01aka186abnob3134.png-65kB
EPT(extended page table)可以看做一個硬件的影子頁表,在Guest中通過增加EPT寄存器,當Guest產生了CR3和頁表的訪問的時候,由於對CR3中的頁表地址的訪問是GPA,當地址為空時候,也就是Page fault后,產生缺頁異常,如果在軟件模擬或者影子頁表的虛擬化方式中,此時會有VM退出,qemu-kvm進程接管並獲取到此異常。但是在EPT的虛擬化方式中,qemu-kvm忽略此異常,Guest並不退出,而是按照傳統的缺頁中斷處理,在缺頁中斷處理的過程中會產生EXIT_REASON_EPT_VIOLATION,Guest退出,qemu-kvm捕獲到異常后,分配物理地址並建立GVA->HPA的映射,並保存到EPT中,將EPT載入到MMU,下次轉換時候直接查詢根據CR3查詢EPT表來完成GVA->HPA的轉換。以后的轉換都由硬件直接完成,大大提高了效率,且不需要為每個進程維護一套頁表,減少了內存開銷。
在筆者的測試中,Guest和HV的內存訪問速率對比為3756MB/s對比4340MB/s。可以看到內存訪問已經很接近宿主機的水平了。

總結

KVM內存的虛擬化就是一個將虛擬機的虛擬內存轉換為宿主機物理內存的過程,Guest使用的依然是宿主機的物理內存,只是在這個過程中怎樣減少轉換帶來的開銷成為優化的主要點。
KVM經過軟件模擬->影子頁表->EPT的技術的進化,效率也越來越高。


免責聲明!

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



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