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

打印 上一主题 下一主题

主题 1798|帖子 1798|积分 5398

《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文件涉及到了对该内核模块的申明:
  1. /*kvm 内核模块入口*/
  2. 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源码注释
  1. /* 在所有 CPU 上初始化 Hyp 模式并设置内存映射,准备 KVM 的使用 */
  2. static __init int kvm_arm_init(void)
  3. {
  4.         int err;
  5.         bool in_hyp_mode;
  6.        
  7.         /*************************************************************/
  8.         /*  1. 完成体系结构相关的初始化 对应linux5.15中 kvm_arch_init   */
  9.         /*************************************************************/
  10.         /*1.1 调用 is_hyp_mode_available 检测硬件是否支持 Hyp 模式*/
  11.         if (!is_hyp_mode_available()) {
  12.                 kvm_info("HYP mode not available\n");
  13.                 return -ENODEV;
  14.         }
  15.        
  16.         /*1.2 检查内核命令行是否禁用了 KVM 功能*/
  17.         if (kvm_get_mode() == KVM_MODE_NONE) {
  18.                 kvm_info("KVM disabled from command line\n");
  19.                 return -ENODEV;
  20.         }
  21.        
  22.         /*1.3 初始化 KVM 使用的系统寄存器表*/
  23.         err = kvm_sys_reg_table_init();
  24.         if (err) {
  25.                 kvm_info("Error initializing system register tables");
  26.                 return err;
  27.         }
  28.        
  29.         /*1.4 检测当前内核是否运行在 Hyp 模式下
  30.          *    如果不在 Hyp 模式,后续会尝试切换到 Hyp 模式
  31.          */
  32.         in_hyp_mode = is_kernel_in_hyp_mode();
  33.        
  34.         /*1.5 检查cpu的相关功能
  35.          *    cpu错误修复功能
  36.          */
  37.         if (cpus_have_final_cap(ARM64_WORKAROUND_DEVICE_LOAD_ACQUIRE) ||
  38.             cpus_have_final_cap(ARM64_WORKAROUND_1508412))
  39.                 kvm_info("Guests without required CPU erratum workarounds can deadlock system!\n" \
  40.                          "Only trusted guests should be used on this system.\n");
  41.        
  42.         /*1.6 设置 KVM 的中间物理地址(IPA)限制*/
  43.         err = kvm_set_ipa_limit();
  44.         if (err)
  45.                 return err;
  46.         /*1.7 初始化 ARM 的可扩展矢量扩展(SVE)支持*/
  47.         err = kvm_arm_init_sve();
  48.         if (err)
  49.                 return err;
  50.         /*1.8 初始化虚拟机标识符(VMID)分配器
  51.          *    VMID 是 ARM 架构中用于区分不同虚拟机的标识符
  52.          */
  53.         err = kvm_arm_vmid_alloc_init();
  54.         if (err) {
  55.                 kvm_err("Failed to initialize VMID allocator.\n");
  56.                 return err;
  57.         }
  58.         /*1.9 如果当前未运行在 Hyp 模式,
  59.          *    则尝试通过 init_hyp_mode 切换到 Hyp 模式
  60.          */
  61.         if (!in_hyp_mode) {
  62.                 err = init_hyp_mode();
  63.                 if (err)
  64.                         goto out_err;
  65.         }
  66.         /*1.10 初始化向量槽,用于管理虚拟机中断向量*/
  67.         err = kvm_init_vector_slots();
  68.         if (err) {
  69.                 kvm_err("Cannot initialise vector slots\n");
  70.                 goto out_hyp;
  71.         }
  72.         /*1.11 初始化其他子系统(如 I/O 管理等)*/
  73.         err = init_subsystems();
  74.         if (err)
  75.                 goto out_hyp;
  76.         /*1.12 根据当前模式,进行日志记录
  77.          *     a. 受保护的 nVHE 模式
  78.          *     b. VHE 模式  *****
  79.          *     c. 普通 Hyp 模式
  80.          */
  81.         if (is_protected_kvm_enabled()) {
  82.                 kvm_info("Protected nVHE mode initialized successfully\n");
  83.         } else if (in_hyp_mode) {
  84.                 kvm_info("VHE mode initialized successfully\n");
  85.         } else {
  86.                 kvm_info("Hyp mode initialized successfully\n");
  87.         }
  88.         /*************************************************************/
  89.         /*                   体系结构相关的初始化结束                  */
  90.         /*************************************************************/
  91.         /*2. 核心操作: KVM 初始化 */
  92.         /*3. 初始化完成标记*/
  93. }
复制代码

kvm_arm_init 函数在 ARM64 架构下通过一系列步骤完成体系结构相干的初始化工作。


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

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源码注释
  1. /* Inits Hyp-mode on all online CPUs */
  2. static int __init init_hyp_mode(void)
  3. {
  4.         u32 hyp_va_bits;
  5.         int cpu;
  6.         int err = -ENOMEM;
  7.         /*1. 确保受保护模式的内存资源可用,
  8.          *   检查保护模式(pKVM)的内存池是否可用
  9.          */
  10.         if (is_protected_kvm_enabled() && !hyp_mem_base)
  11.                 goto out_err;
  12.         /*2. 初始化 Hyp 页表和 Hyp 地址空间映射
  13.          *   为 Hyp 模式分配和管理虚拟地址空间
  14.          */
  15.         err = kvm_mmu_init(&hyp_va_bits);
  16.         if (err)
  17.                 goto out_err;
  18.         /*3.  为每个 CPU 分配 Hyp模式下 一页大小的栈空间*/
  19.         for_each_possible_cpu(cpu) {
  20.                 unsigned long stack_page;
  21.                 stack_page = __get_free_page(GFP_KERNEL);
  22.                 if (!stack_page) {
  23.                         err = -ENOMEM;
  24.                         goto out_err;
  25.                 }
  26.                 per_cpu(kvm_arm_hyp_stack_page, cpu) = stack_page;
  27.         }
  28.         /*4. 初始化 Per-CPU 的 Hyp 区域*/
  29.         for_each_possible_cpu(cpu) {
  30.                 struct page *page;
  31.                 void *page_addr;
  32.                 page = alloc_pages(GFP_KERNEL, nvhe_percpu_order());
  33.                 if (!page) {
  34.                         err = -ENOMEM;
  35.                         goto out_err;
  36.                 }
  37.                 page_addr = page_address(page);
  38.                 memcpy(page_addr, CHOOSE_NVHE_SYM(__per_cpu_start), nvhe_percpu_size());
  39.                 kvm_nvhe_sym(kvm_arm_hyp_percpu_base)[cpu] = (unsigned long)page_addr;
  40.         }
  41.    
  42.         /*5. 创建hyp模式下的 代码段映射*/
  43.         err = create_hyp_mappings(kvm_ksym_ref(__hyp_text_start),
  44.                                   kvm_ksym_ref(__hyp_text_end), PAGE_HYP_EXEC);
  45.         if (err) {
  46.                 kvm_err("Cannot map world-switch code\n");
  47.                 goto out_err;
  48.         }
  49.         /*6. 创建hyp模式下的 只读数据段映射
  50.          *   涉及到.hyp.rodata段 和 .rodata段
  51.          */
  52.         err = create_hyp_mappings(kvm_ksym_ref(__hyp_rodata_start),
  53.                                   kvm_ksym_ref(__hyp_rodata_end), PAGE_HYP_RO);
  54.         if (err) {
  55.                 kvm_err("Cannot map .hyp.rodata section\n");
  56.                 goto out_err;
  57.         }
  58.        
  59.         err = create_hyp_mappings(kvm_ksym_ref(__start_rodata),
  60.                                   kvm_ksym_ref(__end_rodata), PAGE_HYP_RO);
  61.         if (err) {
  62.                 kvm_err("Cannot map rodata section\n");
  63.                 goto out_err;
  64.         }
  65.         /*7. 创建hyp模式下的 bss段映射
  66.          *   涉及 .hyp.bss段 和 .bss段
  67.          */
  68.         err = create_hyp_mappings(kvm_ksym_ref(__hyp_bss_start),
  69.                                   kvm_ksym_ref(__hyp_bss_end), PAGE_HYP);
  70.         if (err) {
  71.                 kvm_err("Cannot map hyp bss section: %d\n", err);
  72.                 goto out_err;
  73.         }
  74.         err = create_hyp_mappings(kvm_ksym_ref(__hyp_bss_end),
  75.                                   kvm_ksym_ref(__bss_stop), PAGE_HYP_RO);
  76.         if (err) {
  77.                 kvm_err("Cannot map bss section\n");
  78.                 goto out_err;
  79.         }
  80.         /*8. 这段代码的功能是为每个 CPU 创建 Hyp 栈,并通过以下方式增强安全性和可靠性:
  81.          *8.1. 分配私有虚拟地址空间:每个 CPU 拥有独立的虚拟地址范围。
  82.          *8.2. 设置保护页:未映射的保护页用于检测栈溢出。
  83.          *8.3. 保存地址信息:保存栈的物理和虚拟地址,便于后续操作。
  84.          *8.4. 逐步检查和回滚:确保初始化过程中任何错误都能安全回滚。
  85.      */
  86.         for_each_possible_cpu(cpu) {
  87.                 struct kvm_nvhe_init_params *params = per_cpu_ptr_nvhe_sym(kvm_init_params, cpu);
  88.                 char *stack_page = (char *)per_cpu(kvm_arm_hyp_stack_page, cpu);
  89.                 unsigned long hyp_addr;
  90.                 /*8.1 分配 Hyp 栈的虚拟地址空间*/
  91.                 err = hyp_alloc_private_va_range(PAGE_SIZE * 2, &hyp_addr);
  92.                 if (err) {
  93.                         kvm_err("Cannot allocate hyp stack guard page\n");
  94.                         goto out_err;
  95.                 }
  96.                 /*8.2 映射栈页面并设置保护页*/
  97.                 err = __create_hyp_mappings(hyp_addr + PAGE_SIZE, PAGE_SIZE,
  98.                                             __pa(stack_page), PAGE_HYP);
  99.                 if (err) {
  100.                         kvm_err("Cannot map hyp stack\n");
  101.                         goto out_err;
  102.                 }
  103.                 /*8.3 保存物理地址和虚拟地址*/
  104.                 params->stack_pa = __pa(stack_page);
  105.                 params->stack_hyp_va = hyp_addr + (2 * PAGE_SIZE);
  106.         }
  107.         /*9. 创建每 CPU 的 Hyp 数据区域映射*/
  108.         for_each_possible_cpu(cpu) {
  109.                 char *percpu_begin = (char *)kvm_nvhe_sym(kvm_arm_hyp_percpu_base)[cpu];
  110.                 char *percpu_end = percpu_begin + nvhe_percpu_size();
  111.                 err = create_hyp_mappings(percpu_begin, percpu_end, PAGE_HYP);
  112.                 if (err) {
  113.                         kvm_err("Cannot map hyp percpu region\n");
  114.                         goto out_err;
  115.                 }
  116.                 cpu_prepare_hyp_mode(cpu, hyp_va_bits);
  117.         }
  118.        
  119.         /*10. 初始化符号*/
  120.         kvm_hyp_init_symbols();
  121.         /*11. 初始化保护模式*/
  122.         if (is_protected_kvm_enabled()) {
  123.                 if (IS_ENABLED(CONFIG_ARM64_PTR_AUTH_KERNEL) &&
  124.                     cpus_have_const_cap(ARM64_HAS_ADDRESS_AUTH))
  125.                         pkvm_hyp_init_ptrauth();
  126.                 init_cpu_logical_map();
  127.                 if (!init_psci_relay()) {
  128.                         err = -ENODEV;
  129.                         goto out_err;
  130.                 }
  131.                 err = kvm_hyp_init_protection(hyp_va_bits);
  132.                 if (err) {
  133.                         kvm_err("Failed to init hyp memory protection\n");
  134.                         goto out_err;
  135.                 }
  136.         }
  137. }
复制代码
我们可以看到该函数完成了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 源码注释

  1. static int __init init_subsystems(void)
  2. {
  3.         int err = 0;
  4.         /*1. 使能每个cpu, 这样系统才能通过EL2*/
  5.         on_each_cpu(cpu_hyp_init, NULL, 1);
  6.         /*2. 注册hyp模式下 cpu的低功耗通知器*/
  7.         hyp_cpu_pm_init();
  8.         /*3.注册hpy模式下的vgic模块  中断管理相关的模块*/
  9.         err = kvm_vgic_hyp_init();
  10.         switch (err) {
  11.         case 0:
  12.                 vgic_present = true;
  13.                 break;
  14.         case -ENODEV:
  15.         case -ENXIO:
  16.                 vgic_present = false;
  17.                 err = 0;
  18.                 break;
  19.         default:
  20.                 goto out;
  21.         }
  22.         /*4. 初始化hyp模式下的timer*/
  23.         err = kvm_timer_hyp_init(vgic_present);
  24.         if (err)
  25.                 goto out;
  26.         kvm_register_perf_callbacks(NULL);
  27. }
复制代码
这里的重点在 初始化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源码注释

  1. /*设置虚拟机环境并确保所有相关子系统的正常运行*/
  2. int kvm_init(unsigned vcpu_size, unsigned vcpu_align, struct module *module)
  3. {
  4.         int r;
  5.         int cpu;
  6.         /*1. 设置cpu热插拔时的回调函数*/
  7. #ifdef CONFIG_KVM_GENERIC_HARDWARE_ENABLING
  8.         r = cpuhp_setup_state_nocalls(CPUHP_AP_KVM_ONLINE, "kvm/cpu:online",
  9.                                       kvm_online_cpu, kvm_offline_cpu);
  10.         if (r)
  11.                 return r;
  12.         /*注册 syscore_ops,用于在系统进入休眠和恢复时处理 KVM 特定操作*/
  13.         register_syscore_ops(&kvm_syscore_ops);
  14. #endif
  15.         /*2. 创建vcpu缓存,
  16.          *   创建一个内存缓存池 kvm_vcpu_cache,
  17.          *   用于分配 kvm_vcpu 结构.
  18.          */
  19.         if (!vcpu_align)
  20.                 vcpu_align = __alignof__(struct kvm_vcpu);
  21.         kvm_vcpu_cache =
  22.                 kmem_cache_create_usercopy("kvm_vcpu", vcpu_size, vcpu_align,
  23.                                            SLAB_ACCOUNT,
  24.                                            offsetof(struct kvm_vcpu, arch),
  25.                                            offsetofend(struct kvm_vcpu, stats_id)
  26.                                            - offsetof(struct kvm_vcpu, arch),
  27.                                            NULL);
  28.         if (!kvm_vcpu_cache) {
  29.                 r = -ENOMEM;
  30.                 goto err_vcpu_cache;
  31.         }
  32.         /*3. 为每个 CPU 分配一个 kick_mask,
  33.          *   用于管理 CPU 中断和调度相关的操作
  34.          */
  35.         for_each_possible_cpu(cpu) {
  36.                 if (!alloc_cpumask_var_node(&per_cpu(cpu_kick_mask, cpu),
  37.                                             GFP_KERNEL, cpu_to_node(cpu))) {
  38.                         r = -ENOMEM;
  39.                         goto err_cpu_kick_mask;
  40.                 }
  41.         }
  42.         /*4. 创建工作队列, 用于处理VM的shutdown操作*/
  43.         r = kvm_irqfd_init();
  44.         if (r)
  45.                 goto err_irqfd;
  46.         /*5. 创建用于分配kvm_async_pf的slab缓存*/
  47.         r = kvm_async_pf_init();
  48.         if (r)
  49.                 goto err_async_pf;
  50.         kvm_chardev_ops.owner = module;
  51.         /*6. 设置调度切换时的回调函数*/
  52.         kvm_preempt_ops.sched_in = kvm_sched_in;
  53.         kvm_preempt_ops.sched_out = kvm_sched_out;
  54.         kvm_init_debug();
  55.          /*7. VFIO操作初始化*/
  56.         r = kvm_vfio_ops_init();
  57.         if (WARN_ON_ONCE(r))
  58.                 goto err_vfio;
  59.         /*8. 注册/dev/kvm 字符设备,使用户空间可以通过该字符设备与kvm交互*/
  60.         r = misc_register(&kvm_dev);
  61.         if (r) {
  62.                 pr_err("kvm: misc device register failed\n");
  63.                 goto err_register;
  64.         }
  65.         ...
  66. }
复制代码

  • 回调函数设置: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 发送指令或吸收数据。

该字符装备的注册, 一共涉及到了三个操作集,设置好了对应的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企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

刘俊凯

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表