一文分析Linux虛擬化KVM-Qemu分析之內(nèi)存虛擬化
說明:
KVM版本:5.9.1
QEMU版本:5.0.0
工具:Source Insight 3.5, Visio
1. 概述
《Linux虛擬化KVM-Qemu分析(二)之ARMv8虛擬化》
文中描述過內(nèi)存虛擬化大體框架,再來回顧一下:
非虛擬化下的內(nèi)存的訪問

CPU訪問物理內(nèi)存前,需要先建立頁表映射(虛擬地址到物理地址的映射),最終通過查表的方式來完成訪問。在ARMv8中,內(nèi)核頁表基地址存放在
TTBR1_EL1
中,用戶空間頁表基地址存放在TTBR0_EL0
中;
虛擬化下的內(nèi)存訪問

虛擬化情況下,內(nèi)存的訪問會(huì)分為兩個(gè)
Stage
,Hypervisor
通過Stage 2
來控制虛擬機(jī)的內(nèi)存視圖,控制虛擬機(jī)是否可以訪問某塊物理內(nèi)存,進(jìn)而達(dá)到隔離的目的;Stage 1
:VA(Virtual Address)->IPA(Intermediate Physical Address)
,Host的操作系統(tǒng)控制Stage 1
的轉(zhuǎn)換;Stage 2
:IPA(Intermediate Physical Address)->PA(Physical Address)
,Hypervisor控制Stage 2
的轉(zhuǎn)換;
猛一看上邊兩個(gè)圖,好像明白了啥,仔細(xì)一想,啥也不明白,本文的目標(biāo)就是將這個(gè)過程講明白。
在開始細(xì)節(jié)講解之前,需要先描述幾個(gè)概念:

Guest OS中的虛擬地址到物理地址的映射,就是典型的常規(guī)操作,參考之前的內(nèi)存管理模塊系列文章;
鋪墊了這么久,來到了本文的兩個(gè)主題:
GPA->HVA
;HVA->HPA
;
開始吧!
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ??


2. GPA->HVA
還記得上一篇文章深入探索Linux虛擬化KVM-Qemu分析之CPU虛擬化中的Sample Code嗎?KVM-Qemu方案中,GPA->HVA的轉(zhuǎn)換,是通過ioctl
中的KVM_SET_USER_MEMORY_REGION
命令來實(shí)現(xiàn)的,如下圖:

找到了入口,讓我們進(jìn)一步揭開神秘的面紗。
2.1 數(shù)據(jù)結(jié)構(gòu)
關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)如下:

虛擬機(jī)使用
slot
來組織物理內(nèi)存,每個(gè)slot
對應(yīng)一個(gè)struct kvm_memory_slot
,一個(gè)虛擬機(jī)的所有slot
構(gòu)成了它的物理地址空間;用戶態(tài)使用
struct kvm_userspace_memory_region
來設(shè)置內(nèi)存slot
,在內(nèi)核中使用struct kvm_memslots
結(jié)構(gòu)來將kvm_memory_slot
組織起來;struct kvm_userspace_memory_region
結(jié)構(gòu)體中,包含了slot
的ID號(hào)用于查找對應(yīng)的slot
,此外還包含了物理內(nèi)存起始地址及大小,以及HVA地址,HVA地址是在用戶進(jìn)程地址空間中分配的,也就是Qemu進(jìn)程地址空間中的一段區(qū)域;
2.2 流程分析
數(shù)據(jù)結(jié)構(gòu)部分已經(jīng)羅列了大體的關(guān)系,那么在KVM_SET_USER_MEMORY_REGION
時(shí),圍繞的操作就是slots
的創(chuàng)建、刪除,更新等操作,話不多說,來圖了:

當(dāng)用戶要設(shè)置內(nèi)存區(qū)域時(shí),最終會(huì)調(diào)用到
__kvm_set_memory_region
函數(shù),在該函數(shù)中完成所有的邏輯處理;__kvm_set_memory_region
函數(shù),首先會(huì)對傳入的struct kvm_userspace_memory_region
的各個(gè)字段進(jìn)行合法性檢測判斷,主要是包括了地址的對齊,范圍的檢測等;根據(jù)用戶傳遞的
slot
索引號(hào),去查找虛擬機(jī)中對應(yīng)的slot
,查找的結(jié)果只有兩種:1)找到一個(gè)現(xiàn)有的slot;2)找不到則新建一個(gè)slot;如果傳入的參數(shù)中
memory_size
為0,那么會(huì)將對應(yīng)slot
進(jìn)行刪除操作;根據(jù)用戶傳入的參數(shù),設(shè)置
slot
的處理方式:KVM_MR_CREATE
,KVM_MR_MOVE
,KVM_MEM_READONLY
;根據(jù)用戶傳遞的參數(shù)決定是否需要分配臟頁的bitmap,標(biāo)識(shí)頁是否可用;
最終調(diào)用
kvm_set_memslot
來設(shè)置和更新slot
信息;
2.2.1 kvm_set_memslot
具體的memslot
的設(shè)置在kvm_set_memslot
函數(shù)中完成,slot
的操作流程如下:

首先分配一個(gè)新的
memslots
,并將原來的memslots
內(nèi)容復(fù)制到新的memslots
中;如果針對
slot
的操作是刪除或者移動(dòng),首先根據(jù)舊的slot id
號(hào)從memslots
中找到原來的slot
,將該slot
設(shè)置成不可用狀態(tài),再將memslots
安裝回去。這個(gè)安裝的意思,就是RCU的assignment操作,不理解這個(gè)的,建議去看看之前的RCU系列文章。由于slot
不可用了,需要解除stage2的映射;kvm_arch_prepare_memory_region
函數(shù),用于處理新的slot
可能跨越多個(gè)用戶進(jìn)程VMA區(qū)域的問題,如果為設(shè)備區(qū)域,還需要將該區(qū)域映射到Guest IPA
中;update_memslots
用于更新整個(gè)memslots
,memslots
基于PFN來進(jìn)行排序的,添加、刪除、移動(dòng)等操作都是基于這個(gè)條件。由于都是有序的,因此可以選擇二分法來進(jìn)行查找操作;將添加新的
slot
后的memslots
安裝回KVM中;kvfree
用于將原來的memslots
釋放掉;
2.2.2 kvm_delete_memslot
kvm_delete_memslot
函數(shù),實(shí)際就是調(diào)用的kvm_set_memslot
函數(shù),只是slot
的操作設(shè)置成KVM_MR_DELETE
而已,不再贅述。
3. HVA->HPA
光有了GPA->HVA,似乎還是跟Hypervisor
沒有太大關(guān)系,到底是怎么去訪問物理內(nèi)存的呢?貌似也沒有看到去建立頁表映射???跟我走吧,帶著問題出發(fā)!
之前內(nèi)存管理相關(guān)文章中提到過,用戶態(tài)程序中分配虛擬地址vma后,實(shí)際與物理內(nèi)存的映射是在page fault
時(shí)進(jìn)行的。那么同樣的道理,我們可以順著這個(gè)思路去查找是否HVA->HPA的映射也是在異常處理的過程中創(chuàng)建的?答案是顯然的。
回顧一下前文深入探索Linux虛擬化KVM-Qemu分析之CPU虛擬化
的一張圖片:

當(dāng)用戶態(tài)觸發(fā)
kvm_arch_vcpu_ioctl_run
時(shí),會(huì)讓Guest OS
去跑在Hypervisor
上,當(dāng)Guest OS
中出現(xiàn)異常退出到Host
時(shí),此時(shí)handle_exit
將對退出的原因進(jìn)行處理;
異常處理函數(shù)arm_exit_handlers
如下,具體調(diào)用選擇哪個(gè)處理函數(shù),是根據(jù)ESR_EL2, Exception Syndrome Register(EL2)
中的值來確定的。
用你那雙水汪汪的大眼睛掃描一下這個(gè)函數(shù)表,發(fā)現(xiàn)ESR_ELx_EC_DABT_LOW
和ESR_ELx_EC_IABT_LOW
兩個(gè)異常,這不就是指令異常和數(shù)據(jù)異常嗎,我們大膽的猜測,HVA->HPA
映射的建立就在kvm_handle_guest_abort
函數(shù)中。
3.1?kvm_handle_guest_abort
先來補(bǔ)充點(diǎn)知識(shí)點(diǎn),可以更方便的理解接下里的內(nèi)容:
Guest OS在執(zhí)行到敏感指令時(shí),產(chǎn)生EL2異常,CPU切換模式并跳轉(zhuǎn)到
EL2
的el1_sync
(arch/arm64/kvm/hyp/entry-hyp.S
)異常入口;CPU的
ESR_EL2
寄存器記錄了異常產(chǎn)生的原因;Guest退出到kvm后,kvm根據(jù)異常產(chǎn)生的原因進(jìn)行對應(yīng)的處理。
簡要看一下ESR_EL2
寄存器:

EC
:Exception class,異常類,用于標(biāo)識(shí)異常的原因;ISS
:Instruction Specific Syndrome,ISS域定義了更詳細(xì)的異常細(xì)節(jié);在
kvm_handle_guest_abort
函數(shù)中,多處需要對異常進(jìn)行判斷處理;
kvm_handle_guest_abort
函數(shù),處理地址訪問異常,可以分為兩類:
常規(guī)內(nèi)存訪問異常,包括未建立頁表映射、讀寫權(quán)限等;
IO內(nèi)存訪問異常,IO的模擬通常需要Qemu來進(jìn)行模擬;
先看一下kvm_handle_guest_abort
函數(shù)的注釋吧:
到達(dá)Host的abort都是由于缺乏Stage 2頁表轉(zhuǎn)換條目導(dǎo)致的,這個(gè)可能是Guest需要分配更多內(nèi)存而必須為其分配內(nèi)存頁,或者也可能是Guest嘗試去訪問IO空間,IO操作由用戶空間來模擬的。兩者的區(qū)別是觸發(fā)異常的IPA地址是否已經(jīng)在用戶空間中注冊為標(biāo)準(zhǔn)的RAM;
調(diào)用流程來了:

kvm_vcpu_trap_get_fault_type
用于獲取ESR_EL2
的數(shù)據(jù)異常和指令異常的fault status code
,也就是ESR_EL2
的ISS域;kvm_vcpu_get_fault_ipa
用于獲取觸發(fā)異常的IPA地址;kvm_vcpu_trap_is_iabt
用于獲取異常類,也就是ESR_EL2
的EC
,并且判斷是否為ESR_ELx_IABT_LOW
,也就是指令異常類型;kvm_vcpu_dabt_isextabt
用于判斷是否為同步外部異常,同步外部異常的情況下,如果支持RAS,Host能處理該異常,不需要將異常注入給Guest;異常如果不是
FSC_FAULT
,FSC_PERM
,FSC_ACCESS
三種類型的話,直接返回錯(cuò)誤;gfn_to_memslot
,gfn_to_hva_memslot_prot
這兩個(gè)函數(shù),是根據(jù)IPA去獲取到對應(yīng)的memslot和HVA地址,這個(gè)地方就對應(yīng)到了上文中第二章節(jié)中地址關(guān)系的建立了,由于建立了連接關(guān)系,便可以通過IPA去找到對應(yīng)的HVA;如果注冊了RAM,能獲取到正確的HVA,如果是IO內(nèi)存訪問,那么HVA將會(huì)被設(shè)置成
KVM_HVA_ERR_BAD
。kvm_is_error_hva
或者(write_fault && !writable)
代表兩種錯(cuò)誤:1)指令錯(cuò)誤,向Guest注入指令異常;2)IO訪問錯(cuò)誤,IO訪問又存在兩種情況:2.1)Cache維護(hù)指令,則直接跳過該指令;2.2)正常的IO操作指令,調(diào)用io_mem_abort
進(jìn)行IO模擬操作;handle_access_fault
用于處理訪問權(quán)限問題,如果內(nèi)存頁無法訪問,則對其權(quán)限進(jìn)行更新;user_mem_abort
,用于分配更多的內(nèi)存,實(shí)際上就是完成Stage 2頁表映射的建立,根據(jù)異常的IPA地址,已經(jīng)對應(yīng)的HVA,建立映射,細(xì)節(jié)的地方就不表了。
原文作者:LoyenWang
