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

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

本系列文章将对Linux在RISCV架构下KVM虚拟化的实现方法进行梳理与源码分析,主要侧重点放在RISCV架构下,KVM模块初始化、vcpu虚拟、内存虚拟、以及中断虚拟(AIA)的实现上。

在进行正式介绍之前,有必要对文章引用进行提前说明。本系列文章参考了大量的博客、文章以及书籍:

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

kvm作为内核模块插入内核中,来实现对CPU, 内存,中断的虚拟化, 其中IO的虚拟化由qemu负责;

本文章将基于Linux6.5内核中 RISCV 架构下kvm实现细节进行源码分析

既然KVM是以内核模块的形式插入内核的,那么我们便可以从module_init()入手, 在arch/riscv/kvm/main.c文件涉及到了对该内核模块的声名:

/*kvm 内核模块入口*/
module_init(riscv_kvm_init);

进到该初始化函数中一探究竟, riscv_kvm_init函数是在RISCV架构下 初始化kvm的入口函数, 他的主要功能是:

    1. 完成体系结构相关的初始化;
    2. 执行核心函数: kvm_init()来初始化真正的KVM模块;

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

  • RISCV架构下, 如何进行体系结构相关的检查和初始化的;
  • kvm_init函数是如何将kvm模块进行真正的初始化的 (包含了各类回调函数的设置,资源分配,以及设备注册等) ;

1.1 RISCV 体系结构相关初始化

我们先来看一下riscv_kvm_ini()函数中相关的源码:

arch/ariscv/kvm/main/riscv_kvm_init源码注释
在这里插入图片描述

/**
 * @brief riscv 中kvm模块的入口函数
 **/
static int __init riscv_kvm_init(void)
{
	int rc;
	const char *str;

	/*1. 检查 RISC-V Hypervisor 扩展是否可用
	 */
	if (!riscv_isa_extension_available(NULL, h)) {
		kvm_info("hypervisor extension not available\n");
		return -ENODEV;
	}

	/*2. 检查 SBI 版本
	 *   SBI 是 RISC-V 的标准化接口,用于与底层固件交互
	 */
	if (sbi_spec_is_0_1()) {
		kvm_info("require SBI v0.2 or higher\n");
		return -ENODEV;
	}

	/*3. 检查是否支持 SBI 的 RFENCE 扩展,
	 *   该扩展用于同步多个核的内存访问
	 */
	if (!sbi_probe_extension(SBI_EXT_RFENCE)) {
		kvm_info("require SBI RFENCE extension\n");
		return -ENODEV;
	}

	/*4. 检测并设置 G-stage 页表模式
	 */
	kvm_riscv_gstage_mode_detect();//检测并配置二级页表模式

	/*5. VMID检测*/
	kvm_riscv_gstage_vmid_detect();//检测并配置 VMID(虚拟机标识符)

	/*6. 初始化 AIA 以及中断虚拟化的一些全局参数;
	 *   AIA 用于管理更高级的中断功能
	 */
	rc = kvm_riscv_aia_init();
	if (rc && rc != -ENODEV)
		return rc;

	kvm_info("hypervisor extension available\n");

	/*7. 配置 G-stage 页表格式
	 */
	switch (kvm_riscv_gstage_mode()) {
	case HGATP_MODE_SV32X4:
		str = "Sv32x4";
		break;
	case HGATP_MODE_SV39X4:
		str = "Sv39x4";
		break;
	case HGATP_MODE_SV48X4:
		str = "Sv48x4";
		break;
	case HGATP_MODE_SV57X4:
		str = "Sv57x4";
		break;
	default:
		return -ENODEV;
	}
	kvm_info("using %s G-stage page table format\n", str);//打印系统中可用的 VMID 位数

	kvm_info("VMID %ld bits available\n", kvm_riscv_gstage_vmid_bits());

	/*8. 如果 AIA 可用,打印支持的外部中断数量
	 */
	if (kvm_riscv_aia_available())
		kvm_info("AIA available with %d guest external interrupts\n",
			 kvm_riscv_aia_nr_hgei);

	/*9. 调用 kvm_init 初始化 KVM 核心模块
	 */
	rc = kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);
	if (rc) {
		kvm_riscv_aia_exit();
		return rc;
	}

	return 0;
}

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

KVM 初始化逻辑均包括硬件支持检查、页表初始化、中断管理、VMID 检测和字符设备注册等核心步骤;他会先进行硬件方面的检查,再对内存相关部分进行初始化,最重要的一点是进行中断虚拟化的支持;

  • 硬件支持检查:
    1. 检查Hypervisor 扩展是否可用
    2. 检查 SBI 版本和 RFENCE 扩展支持
  • 内存管理初始化:
    1. 检测和配置 G-stage 页表模式
    2. 检测和设置 VMID 位数,支持多虚拟机隔离
  • 中断虚拟化支持:
    1. 初始化 AIA 中断架构,支持高级中断管理

在这里插入图片描述

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;
	}

	...
}
  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虚拟化时再深入分析;

在这里插入图片描述

我们把重点放到字符设备驱动注册上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的寄存器等;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值