Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基說明:數據結構
https://www.cnblogs.com/LoyenWang/
《Linux虛擬化KVM-Qemu分析(二)之ARMv8虛擬化》
文中描述過內存虛擬化大致框架,再來回顧一下:app
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
鋪墊了這麼久,來到了本文的兩個主題:工具
GPA->HVA
;HVA->HPA
;開始吧!this
還記得上一篇文章《Linux虛擬化KVM-Qemu分析(四)之CPU虛擬化(2)》
中的Sample Code嗎?
KVM-Qemu方案中,GPA->HVA的轉換,是經過ioctl
中的KVM_SET_USER_MEMORY_REGION
命令來實現的,以下圖:spa
找到了入口,讓咱們進一步揭開神祕的面紗。操作系統
關鍵的數據結構以下:
debug
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進程地址空間中的一段區域;數據結構部分已經羅列了大致的關係,那麼在KVM_SET_USER_MEMORY_REGION
時,圍繞的操做就是slots
的建立、刪除,更新等操做,話很少說,來圖了:3d
__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
;kvm_set_memslot
來設置和更新slot
信息;具體的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
釋放掉;kvm_delete_memslot
函數,實際就是調用的kvm_set_memslot
函數,只是slot
的操做設置成KVM_MR_DELETE
而已,再也不贅述。
光有了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
函數中。
kvm_handle_guest_abort
先來補充點知識點,能夠更方便的理解接下里的內容:
EL2
的el1_sync
(arch/arm64/kvm/hyp/entry-hyp.S
)異常入口;ESR_EL2
寄存器記錄了異常產生的緣由;簡要看一下ESR_EL2
寄存器:
EC
:Exception class,異常類,用於標識異常的緣由;ISS
:Instruction Specific Syndrome,ISS域定義了更詳細的異常細節;kvm_handle_guest_abort
函數中,多處須要對異常進行判斷處理;kvm_handle_guest_abort
函數,處理地址訪問異常,能夠分爲兩類:
先看一下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. */
調用流程來了:
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;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》
歡迎關注我的公衆號,不按期分享技術文章。