刘俊凯 发表于 2025-3-25 11:30:12

KVM虚拟化 | ARM64:(一)Arm64架构下KVM 模块 初始化流程 及 Linux6.5 源码注释

《KVM虚拟化:ARM64架构下Linux源码分析系列文章》

本系列文章将对Linux在ARM64架构下KVM虚拟化的实现方法举行梳理与源码分析,主要偏重点放在ARM64架构下,KVM模块初始化、vcpu虚拟、内存虚拟、以及中断虚拟(vgic)的实现上。
在举行正式先容之前,有必要对文章引用举行提前阐明。本系列文章参考了大量的博客、文章以及书籍:


[*]Linux虚拟化 - 随笔分类 - LoyenWang - 博客园
[*]泰晓科技:RISCV虚拟化
[*]Sherlock’sblog:虚拟化
[*]albertxu216/LinuxKernel_Learning/Linux6.5源码注释
KVM虚拟化 | ARM64:(一)Arm64架构下KVM 模块 初始化流程 及 Linux6.5 源码注释

kvm作为内核模块插入内核中,来实现对CPU, 内存,中断的虚拟化, 此中IO的虚拟化由qemu负责;
本文章将基于Linux6.5内核中arm架构下kvm实现细节举行源码分析
既然KVM是以内核模块的形式插入内核的,那么我们便可以从module_init()入手, 在arch/arm64/kvm/arm.c文件涉及到了对该内核模块的申明:
/*kvm 内核模块入口*/
module_init(kvm_arm_init);
进到该初始化函数中一探究竟, kvm_arm_init函数是在arm64架构下 初始化kvm的入口函数, 他的主要功能是:


[*]
[*]完成体系结构相干的初始化;
[*]执行核心函数: kvm_init()来初始化真正的KVM模块;
[*]在KVM模块初始化后, 将kvm_arm_initialised标记为初始化成功;

以是本文的主要任务分为以下两点:


[*]ARM64架构下, 怎样举行体系结构相干的检查和初始化的;
[*]kvm_init函数是怎样将kvm模块举行真正的初始化的 (包含了各类回调函数的设置,资源分配,以及装备注册等) ;
1.1 ARM体系结构相干初始化

我们先来看一下kvm_arch_init()函数中相干的源码:
arch/arm64/kvm/arm.c/kvm_arch_init源码注释
/* 在所有 CPU 上初始化 Hyp 模式并设置内存映射,准备 KVM 的使用 */
static __init int kvm_arm_init(void)
{
        int err;
        bool in_hyp_mode;
       
        /*************************************************************/
        /*1. 完成体系结构相关的初始化 对应linux5.15中 kvm_arch_init   */
        /*************************************************************/

        /*1.1 调用 is_hyp_mode_available 检测硬件是否支持 Hyp 模式*/
        if (!is_hyp_mode_available()) {
                kvm_info("HYP mode not available\n");
                return -ENODEV;
        }
       
        /*1.2 检查内核命令行是否禁用了 KVM 功能*/
        if (kvm_get_mode() == KVM_MODE_NONE) {
                kvm_info("KVM disabled from command line\n");
                return -ENODEV;
        }
       
        /*1.3 初始化 KVM 使用的系统寄存器表*/
        err = kvm_sys_reg_table_init();
        if (err) {
                kvm_info("Error initializing system register tables");
                return err;
        }
       
        /*1.4 检测当前内核是否运行在 Hyp 模式下
       *    如果不在 Hyp 模式,后续会尝试切换到 Hyp 模式
       */
        in_hyp_mode = is_kernel_in_hyp_mode();
       
        /*1.5 检查cpu的相关功能
       *    cpu错误修复功能
       */
        if (cpus_have_final_cap(ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) ||
          cpus_have_final_cap(ARM64_WORKAROUND_1508412))
                kvm_info("Guests without required CPU erratum workarounds can deadlock system!\n" \
                       "Only trusted guests should be used on this system.\n");
       
        /*1.6 设置 KVM 的中间物理地址(IPA)限制*/
        err = kvm_set_ipa_limit();
        if (err)
                return err;

        /*1.7 初始化 ARM 的可扩展矢量扩展(SVE)支持*/
        err = kvm_arm_init_sve();
        if (err)
                return err;

        /*1.8 初始化虚拟机标识符(VMID)分配器
       *    VMID 是 ARM 架构中用于区分不同虚拟机的标识符
       */
        err = kvm_arm_vmid_alloc_init();
        if (err) {
                kvm_err("Failed to initialize VMID allocator.\n");
                return err;
        }

        /*1.9 如果当前未运行在 Hyp 模式,
       *    则尝试通过 init_hyp_mode 切换到 Hyp 模式
       */
        if (!in_hyp_mode) {
                err = init_hyp_mode();
                if (err)
                        goto out_err;
        }

        /*1.10 初始化向量槽,用于管理虚拟机中断向量*/
        err = kvm_init_vector_slots();
        if (err) {
                kvm_err("Cannot initialise vector slots\n");
                goto out_hyp;
        }

        /*1.11 初始化其他子系统(如 I/O 管理等)*/
        err = init_subsystems();
        if (err)
                goto out_hyp;

        /*1.12 根据当前模式,进行日志记录
       *   a. 受保护的 nVHE 模式
       *   b. VHE 模式*****
       *   c. 普通 Hyp 模式
       */
        if (is_protected_kvm_enabled()) {
                kvm_info("Protected nVHE mode initialized successfully\n");
        } else if (in_hyp_mode) {
                kvm_info("VHE mode initialized successfully\n");
        } else {
                kvm_info("Hyp mode initialized successfully\n");
        }
        /*************************************************************/
        /*                   体系结构相关的初始化结束                  */
        /*************************************************************/

        /*2. 核心操作: KVM 初始化 */
        /*3. 初始化完成标记*/

}
https://i-blog.csdnimg.cn/direct/60b2e96b1b3c4b74a0afd2777b2d25c1.png#pic_center
kvm_arm_init 函数在 ARM64 架构下通过一系列步骤完成体系结构相干的初始化工作。


[*]首先是对硬件以及内核是否支持虚拟化的检测,包罗检测硬件是否支持 Hyp 模式以及内核是否支持KVM;
[*]其次是对架构功能的初始化,包罗系统寄存器表的初始化、SVE 支持和 VMID 分配器的初始化,检查 CPU 特性与错误修复,设置中心物理地点(IPA)限制,
[*]最重要的是Hyp模式的切换,以及各子系统的初始化;会在背面重点分析
[*]最后,根据运行模式记载日志,确保 KVM 在 ARM64 架构上的虚拟化情况能够稳固、可靠地运行。
https://i-blog.csdnimg.cn/direct/d9938561215f4df5b3a997fcadd8a223.png#pic_center
1.1.1 init_hyp_mode 切换到hyp模式

首先我们看一下init_hyp_mode是怎样初始化并切换到hyp模式的, 该函数核心任务是初始化 ARM64 架构下的 Hyp 模式,为 KVM 提供虚拟化支持; 相干细节如下:


[*]检查条件:确保硬件和内存资源满足 Hyp 模式的运行需求。
[*]设置内存管理:初始化页表,设置 Hyp 模式的地点空间和段映射。
[*]分配资源:为每个 CPU 设置独立的栈和 per-CPU 数据。
[*]支持受保护模式**:启用 pKVM 的内存保护和 PSCI 支持。
Linux6.5/arch/arm64/kvm/arm.c/init_hyp_mode源码注释
/* Inits Hyp-mode on all online CPUs */
static int __init init_hyp_mode(void)
{
        u32 hyp_va_bits;
        int cpu;
        int err = -ENOMEM;

        /*1. 确保受保护模式的内存资源可用,
       *   检查保护模式(pKVM)的内存池是否可用
       */
        if (is_protected_kvm_enabled() && !hyp_mem_base)
                goto out_err;

        /*2. 初始化 Hyp 页表和 Hyp 地址空间映射
       *   为 Hyp 模式分配和管理虚拟地址空间
       */
        err = kvm_mmu_init(&hyp_va_bits);
        if (err)
                goto out_err;

        /*3.为每个 CPU 分配 Hyp模式下 一页大小的栈空间*/
        for_each_possible_cpu(cpu) {
                unsigned long stack_page;

                stack_page = __get_free_page(GFP_KERNEL);
                if (!stack_page) {
                        err = -ENOMEM;
                        goto out_err;
                }

                per_cpu(kvm_arm_hyp_stack_page, cpu) = stack_page;
        }

        /*4. 初始化 Per-CPU 的 Hyp 区域*/
        for_each_possible_cpu(cpu) {
                struct page *page;
                void *page_addr;

                page = alloc_pages(GFP_KERNEL, nvhe_percpu_order());
                if (!page) {
                        err = -ENOMEM;
                        goto out_err;
                }

                page_addr = page_address(page);
                memcpy(page_addr, CHOOSE_NVHE_SYM(__per_cpu_start), nvhe_percpu_size());
                kvm_nvhe_sym(kvm_arm_hyp_percpu_base) = (unsigned long)page_addr;
        }
   
        /*5. 创建hyp模式下的 代码段映射*/
        err = create_hyp_mappings(kvm_ksym_ref(__hyp_text_start),
                                  kvm_ksym_ref(__hyp_text_end), PAGE_HYP_EXEC);
        if (err) {
                kvm_err("Cannot map world-switch code\n");
                goto out_err;
        }

        /*6. 创建hyp模式下的 只读数据段映射
       *   涉及到.hyp.rodata段 和 .rodata段
       */
        err = create_hyp_mappings(kvm_ksym_ref(__hyp_rodata_start),
                                  kvm_ksym_ref(__hyp_rodata_end), PAGE_HYP_RO);
        if (err) {
                kvm_err("Cannot map .hyp.rodata section\n");
                goto out_err;
        }
       
        err = create_hyp_mappings(kvm_ksym_ref(__start_rodata),
                                  kvm_ksym_ref(__end_rodata), PAGE_HYP_RO);
        if (err) {
                kvm_err("Cannot map rodata section\n");
                goto out_err;
        }

        /*7. 创建hyp模式下的 bss段映射
       *   涉及 .hyp.bss段 和 .bss段
       */
        err = create_hyp_mappings(kvm_ksym_ref(__hyp_bss_start),
                                  kvm_ksym_ref(__hyp_bss_end), PAGE_HYP);
        if (err) {
                kvm_err("Cannot map hyp bss section: %d\n", err);
                goto out_err;
        }

        err = create_hyp_mappings(kvm_ksym_ref(__hyp_bss_end),
                                  kvm_ksym_ref(__bss_stop), PAGE_HYP_RO);
        if (err) {
                kvm_err("Cannot map bss section\n");
                goto out_err;
        }


        /*8. 这段代码的功能是为每个 CPU 创建 Hyp 栈,并通过以下方式增强安全性和可靠性:
       *8.1. 分配私有虚拟地址空间:每个 CPU 拥有独立的虚拟地址范围。
       *8.2. 设置保护页:未映射的保护页用于检测栈溢出。
       *8.3. 保存地址信息:保存栈的物理和虚拟地址,便于后续操作。
       *8.4. 逐步检查和回滚:确保初始化过程中任何错误都能安全回滚。
   */
        for_each_possible_cpu(cpu) {
                struct kvm_nvhe_init_params *params = per_cpu_ptr_nvhe_sym(kvm_init_params, cpu);
                char *stack_page = (char *)per_cpu(kvm_arm_hyp_stack_page, cpu);
                unsigned long hyp_addr;

                /*8.1 分配 Hyp 栈的虚拟地址空间*/
                err = hyp_alloc_private_va_range(PAGE_SIZE * 2, &hyp_addr);
                if (err) {
                        kvm_err("Cannot allocate hyp stack guard page\n");
                        goto out_err;
                }

                /*8.2 映射栈页面并设置保护页*/
                err = __create_hyp_mappings(hyp_addr + PAGE_SIZE, PAGE_SIZE,
                                          __pa(stack_page), PAGE_HYP);
                if (err) {
                        kvm_err("Cannot map hyp stack\n");
                        goto out_err;
                }

                /*8.3 保存物理地址和虚拟地址*/
                params->stack_pa = __pa(stack_page);
                params->stack_hyp_va = hyp_addr + (2 * PAGE_SIZE);
        }

        /*9. 创建每 CPU 的 Hyp 数据区域映射*/
        for_each_possible_cpu(cpu) {
                char *percpu_begin = (char *)kvm_nvhe_sym(kvm_arm_hyp_percpu_base);
                char *percpu_end = percpu_begin + nvhe_percpu_size();

                err = create_hyp_mappings(percpu_begin, percpu_end, PAGE_HYP);
                if (err) {
                        kvm_err("Cannot map hyp percpu region\n");
                        goto out_err;
                }

                cpu_prepare_hyp_mode(cpu, hyp_va_bits);
        }
       
        /*10. 初始化符号*/
        kvm_hyp_init_symbols();

        /*11. 初始化保护模式*/
        if (is_protected_kvm_enabled()) {
                if (IS_ENABLED(CONFIG_ARM64_PTR_AUTH_KERNEL) &&
                  cpus_have_const_cap(ARM64_HAS_ADDRESS_AUTH))
                        pkvm_hyp_init_ptrauth();

                init_cpu_logical_map();

                if (!init_psci_relay()) {
                        err = -ENODEV;
                        goto out_err;
                }

                err = kvm_hyp_init_protection(hyp_va_bits);
                if (err) {
                        kvm_err("Failed to init hyp memory protection\n");
                        goto out_err;
                }
        }
}
我们可以看到该函数完成了PGD页表的分配, 完成了各种段的映射, 完成了异常向量表的映射; 并为每个CPU创建hyp模式下的栈等;
1.1.2 init_subsystems 初始化各个子系统

init_subsystems 函数的主要作用是初始化 KVM 在 Hyp 模式下的一些关键子系统,以确保虚拟化情况的正常运行, 这里包罗CPU低功耗通知器模块、VGIC(虚拟化中断控制器)模块、Hyp模式下的Timer模块(用于模仿虚拟化定时中断)等;


[*]
[*]初始化每个CPU的Hyp模式, 使其能够正常进入 EL2(Hypervisor Exception Level);
[*]注册 Hyp 模式的 CPU 低功耗通知器
[*]初始化 VGIC(虚拟化中断控制器)模块
[*]初始化 Hyp 模式下的 Timer
[*]注册性能监控回调,增强对性能的监控本事;

Linux6.5/arch/arm64/kvm/arm.c/init_subsystems 源码注释
https://i-blog.csdnimg.cn/direct/485f2c7a20d84fdfbfd587c25e6c4036.png#pic_center
static int __init init_subsystems(void)
{
        int err = 0;
        /*1. 使能每个cpu, 这样系统才能通过EL2*/
        on_each_cpu(cpu_hyp_init, NULL, 1);

        /*2. 注册hyp模式下 cpu的低功耗通知器*/
        hyp_cpu_pm_init();

        /*3.注册hpy模式下的vgic模块中断管理相关的模块*/
        err = kvm_vgic_hyp_init();
        switch (err) {
        case 0:
                vgic_present = true;
                break;
        case -ENODEV:
        case -ENXIO:
                vgic_present = false;
                err = 0;
                break;
        default:
                goto out;
        }

        /*4. 初始化hyp模式下的timer*/
        err = kvm_timer_hyp_init(vgic_present);
        if (err)
                goto out;

        kvm_register_perf_callbacks(NULL);

}
这里的重点在 初始化VGIC虚拟化中断控制器,以及初始化 Hyp 模式下的 Timer, 二者都是关于kvm中的虚拟中断实现;
1.2 kvm_init KVM初始化

``kvm_init` 的功能是完成 KVM 核心模块的初始化,为虚拟化情况提供运行支持。总体上,它负责建立 KVM 与用户空间的交互接口、初始化虚拟化子系统、分配必要资源,各类回调函数的设置,并确保系统在多核情况下稳固运行。
具体而言,它通过注册 CPU 热插拔回调和性能监控接口,创建 VCPU 缓存和中断管理数据结构,初始化异步页面错误机制和 VFIO 支持,实现对物理装备的虚拟化,并通过注册 /dev/kvm 字符装备为用户空间工具(如 QEMU)提供操作接口,同时具备完善的错误处置惩罚和回滚机制,保障初始化过程的可靠性。
Linux6.5/virt/kvm/kvm_main.c /kvm_init源码注释
https://i-blog.csdnimg.cn/direct/7db0d466deda4fee87fdf5b5804c18e6.png#pic_center
/*设置虚拟机环境并确保所有相关子系统的正常运行*/
int kvm_init(unsigned vcpu_size, unsigned vcpu_align, struct module *module)
{
        int r;
        int cpu;
        /*1. 设置cpu热插拔时的回调函数*/
#ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING
        r = cpuhp_setup_state_nocalls(CPUHP_AP_KVM_ONLINE, "kvm/cpu:online",
                                      kvm_online_cpu, kvm_offline_cpu);
        if (r)
                return r;
        /*注册 syscore_ops,用于在系统进入休眠和恢复时处理 KVM 特定操作*/
        register_syscore_ops(&kvm_syscore_ops);
#endif

        /*2. 创建vcpu缓存,
       *   创建一个内存缓存池 kvm_vcpu_cache,
       *   用于分配 kvm_vcpu 结构.
       */
        if (!vcpu_align)
                vcpu_align = __alignof__(struct kvm_vcpu);
        kvm_vcpu_cache =
                kmem_cache_create_usercopy("kvm_vcpu", vcpu_size, vcpu_align,
                                           SLAB_ACCOUNT,
                                           offsetof(struct kvm_vcpu, arch),
                                           offsetofend(struct kvm_vcpu, stats_id)
                                           - offsetof(struct kvm_vcpu, arch),
                                           NULL);
        if (!kvm_vcpu_cache) {
                r = -ENOMEM;
                goto err_vcpu_cache;
        }

        /*3. 为每个 CPU 分配一个 kick_mask,
       *   用于管理 CPU 中断和调度相关的操作
       */
        for_each_possible_cpu(cpu) {
                if (!alloc_cpumask_var_node(&per_cpu(cpu_kick_mask, cpu),
                                          GFP_KERNEL, cpu_to_node(cpu))) {
                        r = -ENOMEM;
                        goto err_cpu_kick_mask;
                }
        }
        /*4. 创建工作队列, 用于处理VM的shutdown操作*/
        r = kvm_irqfd_init();
        if (r)
                goto err_irqfd;

        /*5. 创建用于分配kvm_async_pf的slab缓存*/
        r = kvm_async_pf_init();
        if (r)
                goto err_async_pf;

        kvm_chardev_ops.owner = module;

        /*6. 设置调度切换时的回调函数*/
        kvm_preempt_ops.sched_in = kvm_sched_in;
        kvm_preempt_ops.sched_out = kvm_sched_out;

        kvm_init_debug();

       /*7. VFIO操作初始化*/
        r = kvm_vfio_ops_init();
        if (WARN_ON_ONCE(r))
                goto err_vfio;

        /*8. 注册/dev/kvm 字符设备,使用户空间可以通过该字符设备与kvm交互*/
        r = misc_register(&kvm_dev);
        if (r) {
                pr_err("kvm: misc device register failed\n");
                goto err_register;
        }

        ...
}

[*]回调函数设置:cpuhp_setup_state_nocall与CPU的热插拔相干,register_reboot_notifer与系统的重启相干,register_syscore_ops与系统的休眠唤醒相干,而这几个模块的回调函数,终极都会去调用体系结构相干的函数去打开或关闭Hypervisor;
[*]资源分配:kmem_cache_create_usercopy与kvm_async_pf_init都是创建slab缓存,用于内核对象的分配;
[*]kvm_vfio_ops_init:VFIO是一个可以安全将装备I/O、中断、DMA导出到用户空间的框架,后续在将IO虚拟化时再深入分析;
我们把重点放到字符装备驱动注册上misc_register ,由于该模块用于用户空间与KVM内核模块举行IO交互;
1.2.1 misc_register 注册字符装备 驱动

该函数用于注册字符装备驱动, 使用户空间程序(如 QEMU)能够与 KVM 内核模块举行交互; /dev/kvm 是一个标准的字符装备文件,通过文件操作(如 ioctl)实现用户空间和内核空间之间的通讯。用户空间程序(如虚拟机管理工具)可以通过该接口向 KVM 发送指令或吸收数据。
https://i-blog.csdnimg.cn/direct/f15aa2bed4be4bf5b9cdb5d23bd2ae72.png#pic_center
该字符装备的注册, 一共涉及到了三个操作集,设置好了对应的ioctl函数,分别是字符装备操作集、kvm-vm操作集、kvm-vcpu操作集;


[*]kvm:代表kvm内核模块,可以通过kvm_dev_ioctl来管理kvm版本信息,以及vm的创建等;
[*]vm:虚拟机实例,可以通过kvm_vm_ioctl函数来创建vcpu,设置内存区间,分配中断等;
[*]vcpu:代表虚拟的CPU,可以通过kvm_vcpu_ioctl来启动或暂停CPU的运行,设置vcpu的寄存器等;

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: KVM虚拟化 | ARM64:(一)Arm64架构下KVM 模块 初始化流程 及 Linux6.5 源码注释