背景
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虛擬化》文中描述過記憶體虛擬化大體框架,再來回顧一下:
- 非虛擬化下的記憶體的訪問

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

- 虛擬化情況下,記憶體的訪問會分為兩個
Stage,Hypervisor通過Stage 2來控制虛擬機的記憶體視圖,控制虛擬機是否可以訪問某塊物理記憶體,進而達到隔離的目的; Stage 1:VA(Virtual Address)->IPA(Intermediate Physical Address),Host的作業系統控制Stage 1的轉換;Stage 2:IPA(Intermediate Physical Address)->PA(Physical Address),Hypervisor控制Stage 2的轉換;
猛一看上邊兩個圖,好像明白了啥,仔細一想,啥也不明白,本文的目標就是將這個程序講明白,
在開始細節講解之前,需要先描述幾個概念:
gva - guest virtual address
gpa - guest physical address
hva - host virtual address
hpa - host physical address

- Guest OS中的虛擬地址到物理地址的映射,就是典型的常規操作,參考之前的記憶體管理模塊系列文章;
鋪墊了這么久,來到了本文的兩個主題:
GPA->HVA;HVA->HPA;
開始吧!
2. GPA->HVA
還記得上一篇文章《Linux虛擬化KVM-Qemu分析(四)之CPU虛擬化(2)》中的Sample Code嗎?
KVM-Qemu方案中,GPA->HVA的轉換,是通過ioctl中的KVM_SET_USER_MEMORY_REGION命令來實作的,如下圖:

找到了入口,讓我們進一步揭開神秘的面紗,
2.1 資料結構
關鍵的資料結構如下:

- 虛擬機使用
slot來組織物理記憶體,每個slot對應一個struct kvm_memory_slot,一個虛擬機的所有slot構成了它的物理地址空間; - 用戶態使用
struct kvm_userspace_memory_region來設定記憶體slot,在內核中使用struct kvm_memslots結構來將kvm_memory_slot組織起來; struct kvm_userspace_memory_region結構體中,包含了slot的ID號用于查找對應的slot,此外還包含了物理記憶體起始地址及大小,以及HVA地址,HVA地址是在用戶行程地址空間中分配的,也就是Qemu行程地址空間中的一段區域;
2.2 流程分析
資料結構部分已經羅列了大體的關系,那么在KVM_SET_USER_MEMORY_REGION時,圍繞的操作就是slots的創建、洗掉,更新等操作,話不多說,來圖了:

- 當用戶要設定記憶體區域時,最侄訓呼叫到
__kvm_set_memory_region函式,在該函式中完成所有的邏輯處理; __kvm_set_memory_region函式,首先會對傳入的struct kvm_userspace_memory_region的各個欄位進行合法性檢測判斷,主要是包括了地址的對齊,范圍的檢測等;- 根據用戶傳遞的
slot索引號,去查找虛擬機中對應的slot,查找的結果只有兩種:1)找到一個現有的slot;2)找不到則新建一個slot; - 如果傳入的引數中
memory_size為0,那么會將對應slot進行洗掉操作; - 根據用戶傳入的引數,設定
slot的處理方式:KVM_MR_CREATE,KVM_MR_MOVE,KVM_MEM_READONLY; - 根據用戶傳遞的引數決定是否需要分配臟頁的bitmap,標識頁是否可用;
- 最終呼叫
kvm_set_memslot來設定和更新slot資訊;
2.2.1 kvm_set_memslot
具體的memslot的設定在kvm_set_memslot函式中完成,slot的操作流程如下:

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

- 當用戶態觸發
kvm_arch_vcpu_ioctl_run時,會讓Guest OS去跑在Hypervisor上,當Guest OS中出現例外退出到Host時,此時handle_exit將對退出的原因進行處理;
例外處理函式arm_exit_handlers如下,具體呼叫選擇哪個處理函式,是根據ESR_EL2, Exception Syndrome Register(EL2)中的值來確定的,
static exit_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,
};
用你那雙水汪汪的大眼睛掃描一下這個函式表,發現ESR_ELx_EC_DABT_LOW和ESR_ELx_EC_IABT_LOW兩個例外,這不就是指令例外和資料例外嗎,我們大膽的猜測,HVA->HPA映射的建立就在kvm_handle_guest_abort函式中,
3.1 kvm_handle_guest_abort
先來補充點知識點,可以更方便的理解接下里的內容:
- Guest OS在執行到敏感指令時,產生EL2例外,CPU切換模式并跳轉到
EL2的el1_sync(arch/arm64/kvm/hyp/entry-hyp.S)例外入口; - CPU的
ESR_EL2暫存器記錄了例外產生的原因; - Guest退出到kvm后,kvm根據例外產生的原因進行對應的處理,
簡要看一下ESR_EL2暫存器:

EC:Exception class,例外類,用于標識例外的原因;ISS:Instruction Specific Syndrome,ISS域定義了更詳細的例外細節;- 在
kvm_handle_guest_abort函式中,多處需要對例外進行判斷處理;
kvm_handle_guest_abort函式,處理地址訪問例外,可以分為兩類:
- 常規記憶體訪問例外,包括未建立頁表映射、讀寫權限等;
- IO記憶體訪問例外,IO的模擬通常需要Qemu來進行模擬;
先看一下kvm_handle_guest_abort函式的注釋吧:
/**
* 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.
*/
- 到達Host的abort都是由于缺乏Stage 2頁表轉換條目導致的,這個可能是Guest需要分配更多記憶體而必須為其分配記憶體頁,或者也可能是Guest嘗試去訪問IO空間,IO操作由用戶空間來模擬的,兩者的區別是觸發例外的IPA地址是否已經在用戶空間中注冊為標準的RAM;
呼叫流程來了:

kvm_vcpu_trap_get_fault_type用于獲取ESR_EL2的資料例外和指令例外的fault status code,也就是ESR_EL2的ISS域;kvm_vcpu_get_fault_ipa用于獲取觸發例外的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這兩個函式,是根據IPA去獲取到對應的memslot和HVA地址,這個地方就對應到了上文中第二章節中地址關系的建立了,由于建立了連接關系,便可以通過IPA去找到對應的HVA;- 如果注冊了RAM,能獲取到正確的HVA,如果是IO記憶體訪問,那么HVA將會被設定成
KVM_HVA_ERR_BAD,kvm_is_error_hva或者(write_fault && !writable)代表兩種錯誤:1)指令錯誤,向Guest注入指令例外;2)IO訪問錯誤,IO訪問又存在兩種情況:2.1)Cache維護指令,則直接跳過該指令;2.2)正常的IO操作指令,呼叫io_mem_abort進行IO模擬操作; handle_access_fault用于處理訪問權限問題,如果記憶體頁無法訪問,則對其權限進行更新;user_mem_abort,用于分配更多的記憶體,實際上就是完成Stage 2頁表映射的建立,根據例外的IPA地址,已經對應的HVA,建立映射,細節的地方就不表了,
來龍去脈摸清楚了,那就草草收場吧,下回見了,
參考
《Arm Architecture Registers Armv8, for Armv8-A architecture profile》
歡迎關注個人公眾號,不定期分享技術文章,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/206041.html
標籤:其他
