KVM源码分析(二):KVM初始化

内核版本为6.12,分析的虚拟化技术是基于X86架构intel下的VMX。
在这里插入图片描述

KVM的 Intel VMX支持

module_init宏用于定义模块的初始化函数。当一个内核模块被加载时,这个函数会被执行。

当KVM模块被加载进入内核时,会调用module_init(vmx_init);执行vmx_init方法初始化 KVM的 Intel VMX支持

static int __init vmx_init(void)
{
	int r, cpu;
	if (!kvm_is_vmx_supported())
		return -EOPNOTSUPP;//检查 CPU 是否支持 VT-x   
	hv_init_evmcs();//初始化 Hyper-V Enlightened VMCS(EVMCS)
	r = kvm_x86_vendor_init(&vt_init_ops);//注册 KVM x86 处理器相关操作
	if (r)
		return r;
	r = vmx_setup_l1d_flush(vmentry_l1d_flush_param);//初始化 L1D(一级数据缓存)清理机制
	if (r)
		goto err_l1d_flush;

	for_each_possible_cpu(cpu) {
		INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
		pi_init_cpu(cpu);//为每个 CPU 初始化 VMCS 结构
	}

	vmx_check_vmcs12_offsets();
	if (!enable_ept)
		allow_smaller_maxphyaddr = true;
//设置 Shadow Paging 相关参数  EPT(Extended Page Tables):Intel 的二级地址转换技术,用于 高效管理 Guest OS 的内存。
	r = kvm_init(sizeof(struct vcpu_vmx), __alignof__(struct vcpu_vmx),
		     THIS_MODULE);//调用 kvm_init() 完成 KVM 核心初始化
	if (r)
		goto err_kvm_init;
	return 0;
err_kvm_init:
	__vmx_exit();
err_l1d_flush:
	kvm_x86_vendor_exit();
	return r;
}

接下来调用KVM_init方法完成核心的初始化,这个函数是 KVM 的 核心初始化入口,决定了 KVM 是否能在系统中成功运行。

kvm_init

int kvm_init(unsigned vcpu_size, unsigned vcpu_align, struct module *module)
{
	int r;
	int cpu;

	//创建 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,//vcpu_size 和 vcpu_align 分别定义 vCPU 结构的大小和对齐方式.
					   offsetof(struct kvm_vcpu, arch),
					   offsetofend(struct kvm_vcpu, stats_id)
					   - offsetof(struct kvm_vcpu, arch),
					   NULL);//创建 kmem_cache,用于分配 struct kvm_vcpu 结构体(虚拟 CPU)
	if (!kvm_vcpu_cache)
		return -ENOMEM;
//为每个 CPU 分配 cpu_kick_mask
	for_each_possible_cpu(cpu) {
		if (!alloc_cpumask_var_node(&per_cpu(cpu_kick_mask, cpu),//cpu_kick_mask 是 每个 CPU 的 CPU Mask,用于 KVM 处理 vCPU 的唤醒
					    GFP_KERNEL, cpu_to_node(cpu))) {
			r = -ENOMEM;
			goto err_cpu_kick_mask;//为每个可能的 CPU 分配 cpumask 结构
		}
	}
	r = kvm_irqfd_init();//初始化 IRQFD,允许QEMU通过 事件通知 KVM 触发虚拟中断
	if (r)
		goto err_irqfd;

	r = kvm_async_pf_init();//初始化异步缺页异常机制
	if (r)
		goto err_async_pf;

	kvm_chardev_ops.owner = module;
	kvm_vm_fops.owner = module;
	kvm_vcpu_fops.owner = module;
	kvm_device_fops.owner = module;//设置 KVM 设备文件的所有者
//这四个 fops 结构用于 /dev/kvm 及相关设备操作,确保 module 在被卸载前不会释放。
	kvm_preempt_ops.sched_in = kvm_sched_in;
	kvm_preempt_ops.sched_out = kvm_sched_out;
//设置 KVM 的调度钩子
	kvm_init_debug();
//负责 KVM 的调试机制,用于 调试 KVM 运行状态
	r = kvm_vfio_ops_init();//初始化 VFIO(Virtual Function I/O),允许 KVM 进行 PCI 直通(PCI Passthrough)
	if (WARN_ON_ONCE(r))
		goto err_vfio;

	kvm_gmem_init(module);
//kvm_gmem_init() 初始化 KVM Guest Memory(GMem) 机制
	r = kvm_init_virtualization();/r = kvm_init_virtualization();
if (r)
    goto err_virt;
//初始化 KVM 的虚拟化支持
	if (r)
		goto err_virt;
	r = misc_register(&kvm_dev);//注册 /dev/kvm 设备
	if (r) {
		pr_err("kvm: misc device register failed\n");
		goto err_register;
	}
	return 0;
err_register:
	kvm_uninit_virtualization();
err_virt:
	kvm_vfio_ops_exit();
err_vfio:
	kvm_async_pf_deinit();
err_async_pf:
	kvm_irqfd_exit();
err_irqfd:
err_cpu_kick_mask:
	for_each_possible_cpu(cpu)
		free_cpumask_var(per_cpu(cpu_kick_mask, cpu));
	kmem_cache_destroy(kvm_vcpu_cache);
	return r;
}

/dev/kvm 如何工作

kvm_init() 最终会注册 /dev/kvm 设备,其定义如下:

static struct miscdevice kvm_dev = {
	KVM_MINOR,
	"kvm",
	&kvm_chardev_ops,
};

kvm_chardev_ops是/dev/kvm的操作集合:

static struct file_operations kvm_chardev_ops = {
	.unlocked_ioctl = kvm_dev_ioctl,//处理 ioctl 系统调用,KVM 主要通过 ioctl 进行控制
	.llseek		= noop_llseek,
	KVM_COMPAT(kvm_dev_ioctl),//适配 32 位和 64 位 ioctl 调用
};

在这个结构体中,主要定义了该设备的操作接口,即kvm_dev_ioctl:

kvm_dev_ioctl

该函数是 /dev/kvm 设备的 ioctl 处理入口,用于处理 KVM 设备级别的 ioctl 请求。主要作用是根据传入的 ioctl 命令,执行不同的 KVM 操作。

static long kvm_dev_ioctl(struct file *filp,unsigned int ioctl, unsigned long arg)
{
    int r = -EINVAL;  // 默认返回无效参数

    switch (ioctl) {
    case KVM_GET_API_VERSION:
        if (arg)  // 这个 ioctl不接受参数
            goto out;
        r = KVM_API_VERSION;  // 返回 KVM API 版本
        break;

    case KVM_CREATE_VM:
        r = kvm_dev_ioctl_create_vm(arg);  // 创建虚拟机
        break;

    case KVM_CHECK_EXTENSION:
        r = kvm_vm_ioctl_check_extension_generic(NULL, arg);  // 查询 KVM 扩展功能
        break;

    case KVM_GET_VCPU_MMAP_SIZE:
        if (arg)  // 这个 ioctl 也不接受参数
            goto out;
        r = PAGE_SIZE;  // struct kvm_run结构体大小
#ifdef CONFIG_X86
        r += PAGE_SIZE;  // 额外的 PIO 数据页(仅限 x86 架构)
#endif
#ifdef CONFIG_KVM_MMIO
        r += PAGE_SIZE;  // 额外的 MMIO 数据页
#endif
        break;
    default:
        return kvm_arch_dev_ioctl(filp, ioctl, arg);  // 交给架构相关的 ioctl 处理
    }
out:
    return r;
}

关键 ioctl 命令解析

ioctl 命令作用
KVM_GET_API_VERSION获取 KVM API 版本
KVM_CREATE_VM调用 kvm_dev_ioctl_create_vm()创建虚拟机
KVM_CHECK_EXTENSION检查 KVM 是否支持某个扩展
KVM_GET_VCPU_MMAP_SIZE获取 kvm_run 结构体的 mmap 大小
默认调用 kvm_arch_dev_ioctl() 交给架构相关代码处理

接下来是 kvm_create_vm(),看看 KVM 是如何创建虚拟机的。

KVM_CREAT_VM

KVM_CREATE_VM 由 QEMU 调用,用于创建 KVM 虚拟机,kvm_dev_ioctl_create_vm(arg) 负责实际的虚拟机创建逻辑,其作用是 创建一个新的 KVM 虚拟机 并返回文件描述符:

static int kvm_dev_ioctl_create_vm(unsigned long type)
{
    char fdname[ITOA_MAX_LEN + 1]; // 存放文件描述符名称
    int r, fd;
    struct kvm *kvm;
    struct file *file;

...........

    //  创建 KVM 虚拟机
    kvm = kvm_create_vm(type, fdname);
    if (IS_ERR(kvm)) {   // 处理创建失败情况
        r = PTR_ERR(kvm);
        goto put_fd;
    }

...............
    return r;
}
步骤作用
get_unused_fd_flags(O_CLOEXEC)申请一个新的文件描述符
snprintf(fdname, sizeof(fdname), "%d", fd);将文件描述符转换为字符串
kvm_create_vm(type, fdname);调用 kvm_create_vm() 创建 VM
anon_inode_getfile("kvm-vm", &kvm_vm_fops, kvm, O_RDWR);创建匿名 inode,并绑定 kvm_vm_fops,用于操作/dev/kvm
kvm_uevent_notify_change(KVM_EVENT_CREATE_VM, kvm);发送 KVM_EVENT_CREATE_VM 事件
fd_install(fd, file);绑定 fdfile,完成 /dev/kvm 设备的 VM 创建

anon_inode_getfile创建了一个匿名inode,绑定了kvm_vm_fops,接着file 结构体 绑定fd,这样用户态就可以通过 fd 访问 KVM 虚拟机。

static struct file_operations kvm_vm_fops = {
	.release        = kvm_vm_release,
	.unlocked_ioctl = kvm_vm_ioctl,
	.llseek		= noop_llseek,
	KVM_COMPAT(kvm_vm_compat_ioctl),
};

这里的kvm_vm_ioctl负责处理VM级别的**ioctl**(比如VCPU的创建)。

kvm_create_vm

创建虚拟机的关键函数为kvm_create_vm,其关键内容如下:

static struct kvm *kvm_create_vm(unsigned long type, const char *fdname)
{
    struct kvm *kvm = kvm_arch_alloc_vm(); //  分配 kvm 结构
    struct kvm_memslots *slots;
    int r, i, j;

    if (!kvm)
        return ERR_PTR(-ENOMEM);

    //  绑定当前进程的 mm 结构
    KVM_MMU_LOCK_INIT(kvm);
    mmgrab(current->mm);
    kvm->mm = current->mm;

    //  初始化 KVM 相关锁、事件
    kvm_eventfd_init(kvm);
    mutex_init(&kvm->lock);
    mutex_init(&kvm->irq_lock);
    mutex_init(&kvm->slots_lock);
    mutex_init(&kvm->slots_arch_lock);
    spin_lock_init(&kvm->mn_invalidate_lock);
    rcuwait_init(&kvm->mn_memslots_update_rcuwait);
    xa_init(&kvm->vcpu_array);
#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
    xa_init(&kvm->mem_attr_array);
#endif
    INIT_LIST_HEAD(&kvm->gpc_list);
    spin_lock_init(&kvm->gpc_lock);

    INIT_LIST_HEAD(&kvm->devices);
    kvm->max_vcpus = KVM_MAX_VCPUS;

    BUILD_BUG_ON(KVM_MEM_SLOTS_NUM > SHRT_MAX);

    kvm->debugfs_dentry = ERR_PTR(-ENOENT);

    //  设置 VM 统计 ID
    snprintf(kvm->stats_id, sizeof(kvm->stats_id), "kvm-%d",
             task_pid_nr(current));

    //  初始化 SRCU 结构
    r = -ENOMEM;
    if (init_srcu_struct(&kvm->srcu))
        goto out_err_no_srcu;
    if (init_srcu_struct(&kvm->irq_srcu))
        goto out_err_no_irq_srcu;

    //  初始化 IRQ 路由
    r = kvm_init_irq_routing(kvm);
    if (r)
        goto out_err_no_irq_routing;

    refcount_set(&kvm->users_count, 1);

    //  初始化 memslots(用于管理 VM 的内存映射)
    for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
        for (j = 0; j < 2; j++) {
            slots = &kvm->__memslots[i][j];
            atomic_long_set(&slots->last_used_slot, (unsigned long)NULL);
            slots->hva_tree = RB_ROOT_CACHED;
            slots->gfn_tree = RB_ROOT;
            hash_init(slots->id_hash);
            slots->node_idx = j;
            slots->generation = i;  // 保证不同地址空间 generation 不同
        }
        rcu_assign_pointer(kvm->memslots[i], &kvm->__memslots[i][0]);
    }

    //  初始化 I/O 总线
    r = -ENOMEM;
    for (i = 0; i < KVM_NR_BUSES; i++) {
        rcu_assign_pointer(kvm->buses[i],
            kzalloc(sizeof(struct kvm_io_bus), GFP_KERNEL_ACCOUNT));
        if (!kvm->buses[i])
            goto out_err_no_arch_destroy_vm;
    }

    //  调用架构相关的 `init` 函数
    r = kvm_arch_init_vm(kvm, type);
    if (r)
        goto out_err_no_arch_destroy_vm;

    //  启用虚拟化
    r = kvm_enable_virtualization();
    if (r)
        goto out_err_no_disable;

#ifdef CONFIG_HAVE_KVM_IRQCHIP
    INIT_HLIST_HEAD(&kvm->irq_ack_notifier_list);
#endif

    //  初始化 MMU 通知
    r = kvm_init_mmu_notifier(kvm);
    if (r)
        goto out_err_no_mmu_notifier;

    //  初始化合并 MMIO 机制
    r = kvm_coalesced_mmio_init(kvm);
    if (r < 0)
        goto out_no_coalesced_mmio;

    //  创建 debugfs 入口
    r = kvm_create_vm_debugfs(kvm, fdname);
    if (r)
        goto out_err_no_debugfs;

    //  调用 `arch` 相关的 `post_init` 函数
    r = kvm_arch_post_init_vm(kvm);
    if (r)
        goto out_err;

    //  添加到全局 `vm_list`
    mutex_lock(&kvm_lock);
    list_add(&kvm->vm_list, &vm_list);
    mutex_unlock(&kvm_lock);

    //  初始化 KVM 电源管理
    preempt_notifier_inc();
    kvm_init_pm_notifier(kvm);

    return kvm;

out_err:
    kvm_destroy_vm_debugfs(kvm);
out_err_no_debugfs:
    kvm_coalesced_mmio_free(kvm);
out_no_coalesced_mmio:
#ifdef CONFIG_KVM_GENERIC_MMU_NOTIFIER
    if (kvm->mmu_notifier.ops)
        mmu_notifier_unregister(&kvm->mmu_notifier, current->mm);
#endif
out_err_no_mmu_notifier:
    kvm_disable_virtualization();
out_err_no_disable:
    kvm_arch_destroy_vm(kvm);
out_err_no_arch_destroy_vm:
    WARN_ON_ONCE(!refcount_dec_and_test(&kvm->users_count));
    for (i = 0; i < KVM_NR_BUSES; i++)
        kfree(kvm_get_bus(kvm, i));
    kvm_free_irq_routing(kvm);
out_err_no_irq_routing:
    cleanup_srcu_struct(&kvm->irq_srcu);
out_err_no_irq_srcu:
    cleanup_srcu_struct(&kvm->srcu);
out_err_no_srcu:
    kvm_arch_free_vm(kvm);
    mmdrop(current->mm);
    return ERR_PTR(r);
}

主要做了这些事情:

步骤作用
1️⃣ kvm_arch_alloc_vm()分配 kvm 结构体(体系架构相关)
2️⃣ 绑定 current->mmKVM 进程绑定 mm 结构(管理内存)
3️⃣ 初始化锁 & 事件互斥锁、spinlockRCU
4️⃣ kvm_init_irq_routing()初始化 IRQ 路由
5️⃣ memslots 初始化管理 KVM 虚拟机的物理内存映射
6️⃣ kvm_arch_init_vm(kvm, type)体系架构相关 VM 初始化
7️⃣ kvm_enable_virtualization()开启虚拟化(例如 VMXON
8️⃣ kvm_init_mmu_notifier()注册 MMU Notifier 机制
9️⃣ kvm_coalesced_mmio_init()初始化 Coalesced MMIO 机制
🔟 kvm_create_vm_debugfs()debugfs 中注册 KVM 信息
1️⃣1️⃣ kvm_arch_post_init_vm()体系架构相关的 post_init
1️⃣2️⃣ list_add(&kvm->vm_list, &vm_list);添加到 vm_list
1️⃣3️⃣ kvm_init_pm_notifier(kvm);处理电源管理通知

主要初始化 KVM 虚拟机的 基础设施(如 memslotsI/O busesirq routing)。下面主要 比较重要的:

kvm_arch_alloc_vm

该方法分配了KVM的结构体,与架构有关,X86下代码如下:

static inline struct kvm *kvm_arch_alloc_vm(void)
{
    return __vmalloc(kvm_x86_ops.vm_size, GFP_KERNEL_ACCOUNT | __GFP_ZERO);
}

这里使用 __vmalloc() 分配一块内存,大小为 kvm_x86_ops.vm_size,并初始化为 0

kvm_x86_ops.vm_size又是什么?

kvm_x86_ops 提供了在X86上的所有操作函数集,比如虚拟机和虚拟 CPU的创建与销毁、内存管理等。在 Intel VMX 虚拟化环境下,其定义为:

struct kvm_x86_ops vt_x86_ops __initdata = {
...........
	.vm_size = sizeof(struct kvm_vmx),
...........
}

所以这里分配的内存为sizeof(struct kvm_vmx),其继承了struct kvm,并添加了与Intel架构下VMX相关的字段

struct kvm_vmx {
	struct kvm kvm;
	unsigned int tss_addr; //任务状态段(TSS)地址,常用于任务切换 或 特权级转换
	bool ept_identity_pagetable_done;  //EPT(扩展页表)身份映射地址
	gpa_t ept_identity_map_addr;//存储了 EPT identity 页表的基地址
	/* Posted Interrupt Descriptor (PID) table for IPI virtualization */
	u64 *pid_table;
};//Posted Interrupt Descriptor (PID) Table,用于 虚拟化 IPI(中断处理)

这里同时分析一下struct kvm,KVM虚拟机实例的核心结构体。每个 kvm 结构代表 一个 VM(虚拟机),它管理 vCPU、内存、I/O 设备、MMU(内存管理单元)等资源。此处仅展示重要的一些成员:

MMU(内存管理单元)相关:

mmu_lock 保护 虚拟机的 MMU 数据结构,防止多个线程同时修改 页表,如果支持 **读写锁,KVM 可以 **多个线程读 MMU,写操作加独占锁。否则,使用 spinlock_t,加锁后别的 CPU 不能访问 MMU。

#ifdef KVM_HAVE_MMU_RWLOCK
    rwlock_t mmu_lock;
#else
    spinlock_t mmu_lock;
#endif

内存槽管理

KVM 采用 “内存槽” 方式管理 Guest 物理内存,支持 多地址空间,memslots记录 当前活跃的内存槽,用于GPA到HVA的映射。

struct mutex slots_lock; //保护 memslots 变更
struct mutex slots_arch_lock;  //保护 架构相关的 memslots 数据
struct kvm_memslots __memslots[KVM_MAX_NR_ADDRESS_SPACES][2];
struct kvm_memslots __rcu *memslots[KVM_MAX_NR_ADDRESS_SPACES];
atomic_t nr_memslots_dirty_logging;//统计 被 KVM 脏页追踪的 memslots

vCPU 相关

struct xarray vcpu_array;//所有 vCPU 组成的动态数组
atomic_t online_vcpus;//当前 可运行的 vCPU 数量
int max_vcpus;//VM 最大支持 vCPU 数
int created_vcpus;//已创建的 vCPU 数
int last_boosted_vcpu;//记录 上次被提升调度优先级的 vCPU

设备 & I/O 相关

struct kvm_io_bus __rcu *buses[KVM_NR_BUSES];//记录 KVM 虚拟设备总线
struct list_head devices;//已注册的 KVM 设备列表

中断相关

KVM 通过 irqfds 处理 Guest -> Host 的 IRQ:

#ifdef CONFIG_HAVE_KVM_IRQCHIP
struct {
    spinlock_t lock;
    struct list_head items;//挂载所有 IRQ 处理回调
    struct list_head resampler_list;//处理 可重采样的 IRQ
    struct mutex resampler_lock;
} irqfds;
#endif

在本次分析中,我们详细梳理了 Linux 6.12 内核中 KVM (基于 Intel VMX) 的初始化过程。从 KVM 模块的加载、体系结构相关的初始化、到 VMX 相关的数据结构设置,我们逐步揭示了 KVM 作为虚拟化核心组件的启动流程。

至此,KVM 已经完成初始化,系统具备了创建 VM 和管理 vCPU 的能力。后续的分析将聚焦于 KVM 在具体场景(如vCPU 调度、EPT 机制等)下的工作方式,以进一步深入理解 KVM 作为 Linux 内核虚拟化组件的运行机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值