【原創】Linux虛擬化KVM-Qemu分析(三)之KVM源碼(1)

背景

  • Read the fucking source code! --By 魯迅
  • A picture is worth a thousand words. --By 高爾基

說明:

  1. KVM版本:5.9.1
  2. QEMU版本:5.0.0
  3. 工具:Source Insight 3.5, Visio
  4. 文章同步在博客園:https://www.cnblogs.com/LoyenWang/

1. 概述

  • 從本文開始將開始source code的系列分析了;
  • KVM作爲內核模塊,可以認爲是一箇中間層,向上對接用戶的控制,向下對接不同架構的硬件虛擬化支持;
  • 本文主要介紹體系架構初始化部分,以及向上的框架;

2. KVM初始化

  • 貝多芬曾經說過,一旦你找到了代碼的入口,你就扼住了軟件的咽喉;
  • 我們的故事,從module_init(arm_init)開始,代碼路徑:arch/arm64/kvm/arm.c

老規矩,先來一張圖(圖片中涉及到的紅色框函數,都是會展開描述的):

  • 內核的功能模塊,基本上的套路就是:1)完成模塊初始化,向系統註冊;2)響應各類請求,這種請求可能來自用戶態,也可能來自異常響應等;
  • kvm的初始化,在kvm_init中完成,既包含了體系結構相關的初始化設置,也包含了各類回調函數的設置,資源分配,以及設備註冊等,只有當初始化完成後,才能響應各類請求,比如創建虛擬機等;
    1. 回調函數設置:cpuhp_setup_state_nocall與CPU的熱插拔相關,register_reboot_notifer與系統的重啓相關,register_syscore_ops與系統的休眠喚醒相關,而這幾個模塊的回調函數,最終都會去調用體系結構相關的函數去打開或關閉Hypervisor
    2. 資源分配:kmem_cache_create_usercopykvm_async_pf_init都是創建slab緩存,用於內核對象的分配;
    3. kvm_vfio_ops_initVFIO是一個可以安全將設備I/O、中斷、DMA導出到用戶空間的框架,後續在將IO虛擬化時再深入分析;
  • 圖片中紅色的兩個函數,是本文分析的內容,其中kvm_arch_init與前文ARMv8硬件虛擬化支持緊密相關,而misc_register與上層操作緊密相關;

2.1 kvm_arch_init

  • It's a big topic, I'll try to put it in a nutshell.
  • 這部分內容,設計ARMv8體系結構,建議先閱讀《Linux虛擬化KVM-Qemu分析(二)之ARMv8虛擬化》
  • 紅色框的函數是需要進一步展開講述的;

  • is_hyp_mode_available用於判斷ARMv8的Hyp模式是否可用,實際是通過判斷__boot_cpu_mode的值來完成,該值是在arch/arm64/kernel/head.S中定義,在啓動階段會設置該值:

  • is_kernel_in_hyp_mode,通過讀取ARMv8的CurrentEL,判斷是否爲CurrentEL_EL2
  • ARM架構中,SVE的實現要求VHE也要實現,這個可以從arch/arm64/Kconfig中看到,SVE的模塊編譯:depends on !KVM || ARM64_VHESVE(scalable vector extension),是AArch64下一代的SIMD(single instruction multiple data)指令集,用於加速高性能計算。其中SIMD如下:

  • init_common_resources,用於設置IPA的地址範圍,將其限制在系統能支持的物理地址範圍之內。stage 2頁表依賴於stage 1頁表代碼,需要遵循一個條件:Stage 1的頁表級數 >= Stage 2的頁表級數;

2.1.1 init_hyp_mode

  • 放眼望去,init_hyp_mode解決的問題就是各種映射,最終都會調用到__create_hyp_mappings,先來解決這個映射問題:

  • 看過之前內存管理子系統的同學,應該熟悉這個頁表映射建立的過程,基本的流程是給定一個虛擬地址區間和物理地址,然後從pgd開始逐級往下去建立映射。ARMv8架構在實際映射過程中,P4D這一級頁表並沒有使用。

讓我們繼續回到init_hyp_mode的正題上來,這個函數完成了PGD頁表的分配,完成了IDMAP代碼段的映射,完成了其他各種段的映射,完成了異常向量表的映射,等等。此外,再補充幾點內容:

  1. ARMv8異常向量表

  • ARMv8架構的AArch64執行態中,每種EL都有16個entry,分爲四類:Synchronous,IRQ,FIQ,SError。以系統啓動時設置hypervisor的異常向量表__hyp_stub_vectors爲例:

  • 當從不同的Exception Level觸發異常時,根據執行狀態,去選擇對應的handler處理,比如上圖中只有el1_sync有效,也就是在EL1狀態觸發EL2時跳轉到該函數;
  1. pushsection/popsection
  • init_hyp_mode函數中,完成各種段的映射,段的定義放置在vmlinux.lds.S中,比如hyp.idmap.text

  • 可以通過pushsection/popsection來在目標文件中來添加一個段,並指定段的屬性,比如"ax"代表可分配和可執行,這個在彙編代碼中經常用到,比如hyp-init.S中,會將代碼都放置在hyp.idmap.text中:

  • 除了pushsection/popsection外,通過#define __hyp_text __section(.hyp.text) notrace __noscs的形式也能將代碼放置在指定的段中;
  1. Hypervisor相關寄存器
  • 講幾個關鍵的相關寄存器:
    1)sctlr_el2(System Control Register):可以用於控制EL2的MMU和Cache相關操作;
    2)ttbr0_el2(Translation Table Base Register 0):用於存放頁表的基地址,上文中提到分配的hyp_pgd就需要設置到該寄存器中;
    3)vbar_el2(Vector Base Address Register):用於存放異常向量表的基地址;

我們需要先明確幾點:

  1. Hyp模式下要執行的代碼,需要先建立起映射;
  2. 映射IDMAP代碼段和其他代碼段,明確這些段中都有哪些函數,這個可以通過pushsection/popsection以及__hyp_text宏可以看出來;
  3. 最終的目標是需要建立好頁表映射,並安裝好異常向量表;

貌似內容比較零碎,最終的串聯與謎題留在下一小節來解答。

2.1.2 init_subsystems

先看一下函數的調用流程:

  • VGICtimer,以及電源管理相關模塊在本文中暫且不深入分析了,本節主要關心cpu_hyp_reinit的功能;
  • 綠色框中的函數,會陷入到EL2進行執行;

看圖中有好幾次異常向量表的設置,此外,還有頁表基地址、棧頁的獲取與設置等,結合上一小節的各類映射,是不是已經有點迷糊了,下邊這張圖會將這些內容串聯起來:

  • 在整個異常向量表創建的過程中,涉及到三個向量表:__hyp_stub_vectors__kvm_hyp_init__kvm_call_hyp,這些代碼都是彙編實現;
  • 在系統啓動過程中(arch/arm64/kernel/head.S),調用到el2_setup函數,在該函數中設置了一個臨時的異常向量表,也就是先打一個樁,這個從名字也可以看出來,該異常向量表中僅實現了el2_synchandler處理函數,可以應對兩種異常:1)設置新的異常向量表;2)重置異常向量表,也就是設置回__hyp_stub_vectors
  • kvm初始化時,調用了__hyp_set_vectors來設置新的異常向量表:__kvm_hyp_init。這個向量表中只實現了__do_hyp_init的處理函數,也就是隻能用來對Hyp模式進行初始化。上文提到過idmap段,這個代碼就放置在idmap段,以前分析內存管理子系統時也提到過idmap,爲什麼需要這個呢?idmap: identity map,也就是物理地址和虛擬地址是一一映射的,防止MMU在使能前後代碼不能執行;
  • __kvm_call_hyp函數,用於在Hyp模式下執行指定的函數,在cpu_hyp_reinit函數中調用了該函數,傳遞的參數包括了新的異常向量表地址,頁表基地址,Hyp的棧地址,per-CPU偏移等,最終會調用__do_hyp_init函數完成相應的設置。

到此,頁表和異常向量表的設置算是完成了。

2.2 misc_register

misc_register用於註冊字符設備驅動,在kvm_init函數中調用此函數完成註冊,以便上層應用程序來使用kvm模塊

  • 字符設備的註冊分爲三級,分別代表kvm, vm, vcpu,上層最終使用底層的服務都是通過ioctl函數來操作;
  • kvm:代表kvm內核模塊,可以通過kvm_dev_ioctl來管理kvm版本信息,以及vm的創建等;
  • vm:虛擬機實例,可以通過kvm_vm_ioctl函數來創建vcpu,設置內存區間,分配中斷等;
  • vcpu:代表虛擬的CPU,可以通過kvm_vcpu_ioctl來啓動或暫停CPU的運行,設置vcpu的寄存器等;

Qemu的使用爲例:

  1. 打開/dev/kvm設備文件;
  2. ioctl(xx, KVM_CREATE_VM, xx)創建虛擬機對象;
  3. ioctl(xx, KVM_CREATE_VCPU, xx)爲虛擬機創建vcpu對象;
  4. ioctl(xx, KVM_RUN, xx)讓vcpu運行起來;

3. 總結

本文主要從兩個方向來介紹了kvm_init

  1. 底層的體系結構相關的初始化,主要涉及的就是EL2的相關設置,比如各個段的映射,異常向量表的安裝,頁表基地址的設置等,當把這些準備工作做完後,才能在硬件上去支持虛擬化的服務請求;
  2. 字符設備註冊,設置好各類ioctl的函數,上層應用程序可以通過字符設備文件,來操作底層的kvm模塊。這部分內容深入的分析,留到後續的文章再展開了;

實際在看代碼過程中,一度爲很多細節絞盡乳汁,對不起,是絞盡腦汁,每有會意,便欣然忘食,一文也無法覆蓋所有內容,草率了。

歡迎關注個人公衆號,不定期更新技術文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章