内核版本为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); | 绑定 fd 到 file ,完成 /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->mm | KVM 进程绑定 mm 结构(管理内存) |
3️⃣ 初始化锁 & 事件 | 互斥锁、spinlock 、RCU 等 |
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 虚拟机的 基础设施(如 memslots
、I/O buses
、irq 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 内核虚拟化组件的运行机制。