linux 内存查看 kvm,Linux虚拟化KVM-Qemu分析(五)之内存虚拟化
原標(biāo)題:Linux虛擬化KVM-Qemu分析(五)之內(nèi)存虛擬化
背景
Read the fucking source code! --By 魯迅
A picture is worth a thousand words. --By 高爾基
說明:
KVM版本:5.9.1
QEMU版本:5.0.0
工具:Source Insight 3.5, Visio
文章同步在博客園: https://www.cnblogs.com/LoyenWang/
1. 概述
《Linux虛擬化KVM-Qemu分析(二)之ARMv8虛擬化》 文中描述過內(nèi)存虛擬化大體框架,再來回顧一下:
非虛擬化下的內(nèi)存的訪問
CPU訪問物理內(nèi)存前,需要先建立頁表映射(虛擬地址到物理地址的映射),最終通過查表的方式來完成訪問。在ARMv8中,內(nèi)核頁表基地址存放在 TTBR1_EL1 中,用戶空間頁表基地址存放在 TTBR0_EL0 中;
虛擬化下的內(nèi)存訪問
虛擬化情況下,內(nèi)存的訪問會分為兩個 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)換;
猛一看上邊兩個圖,好像明白了啥,仔細(xì)一想,啥也不明白,本文的目標(biāo)就是將這個過程講明白。
在開始細(xì)節(jié)講解之前,需要先描述幾個概念:
gva - guest virtualaddress
gpa - guest physical address
hva - host virtualaddress
hpa - host physical address
Guest OS中的虛擬地址到物理地址的映射,就是典型的常規(guī)操作,參考之前的內(nèi)存管理模塊系列文章;
鋪墊了這么久,來到了本文的兩個主題:
GPA->HVA ;
HVA->HPA ;
開始吧!
2. GPA->HVA
還記得上一篇文章 《Linux虛擬化KVM-Qemu分析(四)之CPU虛擬化(2)》 中的Sample Code嗎?KVM-Qemu方案中,GPA->HVA的轉(zhuǎn)換,是通過 ioctl 中的 KVM_SET_USER_MEMORY_REGION 命令來實現(xiàn)的,如下圖:
找到了入口,讓我們進(jìn)一步揭開神秘的面紗。
2.1 數(shù)據(jù)結(jié)構(gòu)
關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)如下:
虛擬機(jī)使用 slot 來組織物理內(nèi)存,每個 slot 對應(yīng)一個 struct kvm_memory_slot ,一個虛擬機(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號用于查找對應(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 時,圍繞的操作就是 slots 的創(chuàng)建、刪除,更新等操作,話不多說,來圖了:
當(dāng)用戶要設(shè)置內(nèi)存區(qū)域時,最終會調(diào)用到 __kvm_set_memory_region 函數(shù),在該函數(shù)中完成所有的邏輯處理;
__kvm_set_memory_region 函數(shù),首先會對傳入的 struct kvm_userspace_memory_region 的各個字段進(jìn)行合法性檢測判斷,主要是包括了地址的對齊,范圍的檢測等;
根據(jù)用戶傳遞的 slot 索引號,去查找虛擬機(jī)中對應(yīng)的 slot ,查找的結(jié)果只有兩種:1)找到一個現(xiàn)有的slot;2)找不到則新建一個slot;
如果傳入的參數(shù)中 memory_size 為0,那么會將對應(yīng) slot 進(jìn)行刪除操作;
根據(jù)用戶傳入的參數(shù),設(shè)置 slot 的處理方式: KVM_MR_CREATE , KVM_MR_MOVE , KVM_MEM_READONLY ;
根據(jù)用戶傳遞的參數(shù)決定是否需要分配臟頁的bitmap,標(biāo)識頁是否可用;
最終調(diào)用 kvm_set_memslot 來設(shè)置和更新 slot 信息;
2.2.1 kvm_set_memslot
具體的 memslot 的設(shè)置在 kvm_set_memslot 函數(shù)中完成, slot 的操作流程如下:
首先分配一個新的 memslots ,并將原來的 memslots 內(nèi)容復(fù)制到新的 memslots 中;
如果針對 slot 的操作是刪除或者移動,首先根據(jù)舊的 slot id 號從 memslots 中找到原來的 slot ,將該 slot 設(shè)置成不可用狀態(tài),再將 memslots 安裝回去。這個安裝的意思,就是RCU的assignment操作,不理解這個的,建議去看看之前的RCU系列文章。由于 slot 不可用了,需要解除stage2的映射;
kvm_arch_prepare_memory_region 函數(shù),用于處理新的 slot 可能跨越多個用戶進(jìn)程VMA區(qū)域的問題,如果為設(shè)備區(qū)域,還需要將該區(qū)域映射到 Guest IPA 中;
update_memslots 用于更新整個 memslots , memslots 基于PFN來進(jìn)行排序的,添加、刪除、移動等操作都是基于這個條件。由于都是有序的,因此可以選擇二分法來進(jìn)行查找操作;
將添加新的 slot 后的 memslots 安裝回KVM中;
kvfree 用于將原來的 memslots 釋放掉;
2.2.2 kvm_delete_memslot
kvm_delete_memslot 函數(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后,實際與物理內(nèi)存的映射是在 page fault 時進(jìn)行的。那么同樣的道理,我們可以順著這個思路去查找是否HVA->HPA的映射也是在異常處理的過程中創(chuàng)建的?答案是顯然的。
回顧一下前文 《Linux虛擬化KVM-Qemu分析(四)之CPU虛擬化(2)》 的一張圖片:
當(dāng)用戶態(tài)觸發(fā) kvm_arch_vcpu_ioctl_run 時,會讓 Guest OS 去跑在 Hypervisor 上,當(dāng) Guest OS 中出現(xiàn)異常退出到 Host 時,此時 handle_exit 將對退出的原因進(jìn)行處理;
異常處理函數(shù) arm_exit_handlers 如下,具體調(diào)用選擇哪個處理函數(shù),是根據(jù) ESR_EL2, Exception Syndrome Register(EL2) 中的值來確定的。
staticexit_handle_fn arm_exit_handlers[] = {
[ 0... ESR_ELx_EC_MAX] = kvm_handle_unknown_ec,
[ESR_ELx_EC_WFx] = kvm_handle_wfx,
[ESR_ELx_EC_CP15_32] = kvm_handle_cp15_32,
[ESR_ELx_EC_CP15_64] = kvm_handle_cp15_64,
[ESR_ELx_EC_CP14_MR] = kvm_handle_cp14_32,
[ESR_ELx_EC_CP14_LS] = kvm_handle_cp14_load_store,
[ESR_ELx_EC_CP14_64] = kvm_handle_cp14_64,
[ESR_ELx_EC_HVC32] = handle_hvc,
[ESR_ELx_EC_SMC32] = handle_smc,
[ESR_ELx_EC_HVC64] = handle_hvc,
[ESR_ELx_EC_SMC64] = handle_smc,
[ESR_ELx_EC_SYS64] = kvm_handle_sys_reg,
[ESR_ELx_EC_SVE] = handle_sve,
[ESR_ELx_EC_IABT_LOW] = kvm_handle_guest_abort,
[ESR_ELx_EC_DABT_LOW] = kvm_handle_guest_abort,
[ESR_ELx_EC_SOFTSTP_LOW]= kvm_handle_guest_debug,
[ESR_ELx_EC_WATCHPT_LOW]= kvm_handle_guest_debug,
[ESR_ELx_EC_BREAKPT_LOW]= kvm_handle_guest_debug,
[ESR_ELx_EC_BKPT32] = kvm_handle_guest_debug,
[ESR_ELx_EC_BRK64] = kvm_handle_guest_debug,
[ESR_ELx_EC_FP_ASIMD] = handle_no_fpsimd,
[ESR_ELx_EC_PAC] = kvm_handle_ptrauth,
};
用你那雙水汪汪的大眼睛掃描一下這個函數(shù)表,發(fā)現(xiàn) ESR_ELx_EC_DABT_LOW 和 ESR_ELx_EC_IABT_LOW 兩個異常,這不就是指令異常和數(shù)據(jù)異常嗎,我們大膽的猜測, HVA->HPA 映射的建立就在 kvm_handle_guest_abort 函數(shù)中。
3.1 kvm_handle_guest_abort
先來補(bǔ)充點知識點,可以更方便的理解接下里的內(nèi)容:
Guest OS在執(zhí)行到敏感指令時,產(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)識異常的原因;
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ù)的注釋吧:
/**
* kvm_handle_guest_abort - handles all 2nd stage aborts
*
* Any abort that gets to the host is almost guaranteed to be caused by a
* missing second stage translation table entry, which can mean that either the
* guest simply needs more memory and we must allocate an appropriate page or it
* can mean that the guest tried to access I/O memory, which is emulated by user
* space. The distinction is based on the IPA causing the fault and whether this
* memory region has been registered as standard RAM by user space.
*/
到達(dá)Host的abort都是由于缺乏Stage 2頁表轉(zhuǎn)換條目導(dǎo)致的,這個可能是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 三種類型的話,直接返回錯誤;
gfn_to_memslot , gfn_to_hva_memslot_prot 這兩個函數(shù),是根據(jù)IPA去獲取到對應(yīng)的memslot和HVA地址,這個地方就對應(yīng)到了上文中第二章節(jié)中地址關(guān)系的建立了,由于建立了連接關(guān)系,便可以通過IPA去找到對應(yīng)的HVA;
如果注冊了RAM,能獲取到正確的HVA,如果是IO內(nèi)存訪問,那么HVA將會被設(shè)置成 KVM_HVA_ERR_BAD 。 kvm_is_error_hva 或者 (write_fault && !writable) 代表兩種錯誤:1)指令錯誤,向Guest注入指令異常;2)IO訪問錯誤,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)存,實際上就是完成Stage 2頁表映射的建立,根據(jù)異常的IPA地址,已經(jīng)對應(yīng)的HVA,建立映射,細(xì)節(jié)的地方就不表了。
來龍去脈摸清楚了,那就草草收場吧,下回見了。
參考
《Arm Architecture Registers Armv8, for Armv8-A architecture profile》 返回搜狐,查看更多
責(zé)任編輯:
總結(jié)
以上是生活随笔為你收集整理的linux 内存查看 kvm,Linux虚拟化KVM-Qemu分析(五)之内存虚拟化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国智能电饭煲行业运营模式及趋势预测分析
- 下一篇: ARMv7 KVM 在 linux中的实