《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源码注释

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源码注释
- /* 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)[cpu] = (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)[cpu];
- 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 源码注释
- 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源码注释

- /*设置虚拟机环境并确保所有相关子系统的正常运行*/
- 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 发送指令或吸收数据。
该字符装备的注册, 一共涉及到了三个操作集,设置好了对应的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企服之家,中国第一个企服评测及商务社交产业平台。 |