- 1. 原理介绍
- 2. 整体流程
- 3. kvm_init: 初始化 kvm 框架
- 3.1. 架构初始化: kvm_arch_init
- 3.1.1. CPU特性支持检查
- 3.1.2. kmem_cache分配(x86_fpu)
- 3.1.3. percpu kvm_shared_msrs
- 3.1.4. mmu 模块相关处理
- 3.1.5. 设置全局的 kvm_x86_ops
- 3.1.6. 设置 MMU 的shadow PTE masks
- 3.1.7. timer初始化
- 3.1.8. lapic初始化
- 3.2. workqueue?
- 3.3. 架构相关的硬件设置
- 3.3.1. vmx/svm 的硬件配置 hardware_setup()
- 3.3.1.1. 设置全局 vmcs_config 和 vmx_capability
- 3.3.1.2. 一堆检查
- 3.3.1.3. posted 中断的 wakeup 处理函数设置
- 3.3.1.4. percpu的VMXON region
- 3.3.2. msr保存到全局变量msrs_to_save[]数组
- 3.3.1. vmx/svm 的硬件配置 hardware_setup()
- 3.4. 检查每个处理器对vmx的兼容性
- 3.5. 注册CPU状态变化的通知函数
- 3.6. 注册reboot时候的通知函数
- 3.7. 给kvm_vcpu分配cache
- 3.8. 赋值file_operations的模块名
- 3.9. 注册设备文件/dev/kvm
- 3.10. 动作注册
- 3.11. debugfs初始化
- 3.12. vfio操作初始化
- 3.1. 架构初始化: kvm_arch_init
- 4. 参考
KVM模块分为三个主要模块:kvm.ko、kvm-intel.ko和kvm-amd.ko,这三个模块在初始化阶段的流程如图5-4所示。
?图5-4 KVM模块初始化阶段:
KVM模块可以编译进内核中,也可以作为内核模块在Linux系统启动完成之后加载。加载时,KVM 根据主机所用的体系架构是 Intel的 VMX技术还是AMD的SVM技术,会采用略微不同的加载流程。
Linux的子模块入口通常通过module_init宏进行定义,由内核进行调用。KVM的初始化流程如图5-5所示。
?
图5-5 KVM的初始化流程:
KVM的初始化步骤分为以下三步。
1)在平台相关的KVM模块中通过module_init宏正式进入KVM的初始化阶段,并且执行相关的硬件初始化准备。(arch/x86/kvm/vmx.c)
2)进入kvm_main.c中的kvm_init函数进行正式的初始化工作,期间进行了一系列子操作。(virt/kvm/kvm_main.c)
注: vmx.c中定义了一个结构体
- 通过kvm_arch_init函数初始化KVM内部的一些数据结构:注册全局变量kvm_x86_ops、初始化MMU等数据结构、初始化Timer定时器架构。(arch/x86/kvm/x86.c)
- 分配KVM内部操作所需要的内存空间。
- 调用kvm_x86_ops的hardware_setup函数进行具体的硬件体系结构的初始化工作。
- 注册sysfs和devfs等API接口信息。
- 最后初始化debugfs的调试信息。
3)进行后续的硬件初始化准备操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | vmx_init() // 初始化入口 ├─ kvm_init(KVM_GET_API_VERSION) // 初始化KVM框架 | ├─ kvm_arch_init() // 架构相关初始化 | | ├─ cpu_has_kvm_support() // CPU是否支持kvm | | ├─ disabled_by_bios() // bios是否禁用vt | | ├─ boot_cpu_has() // CPU是否支持一些特性 | | ├─ kmem_cache_create("x86_fpu") // x86_fpu kmem_cache | | ├─ alloc_percpu() // kvm_shared_msrs | | ├─ kvm_mmu_module_init() // mmu模块初始化 | | ├─ kvm_mmu_set_mask_ptes() // shadow pte mask设置 | | ├─ kvm_timer_init() // 时钟初始化 | | ├─ kvm_lapic_init() // lapic初始化 | ├─ kvm_irqfd_init() // | ├─ kvm_arch_hardware_setup() // | | ├─ kvm_x86_ops->hardware_setup() // CPU是否支持kvm | | | ├─ rdmsrl_safe() // 读msr | | | ├─ store_idt() // 保存idt | | | ├─ setup_vmcs_config() // 建立vmcs_config和vmx_capability | | | ├─ boot_cpu_has() // CPU特性支持 | | | ├─ cpu_has_vmx_vpid() // cpu是否支持vpid | | | ├─ cpu_has_vmx_invvpid() // cpu是否支持invvpid | | | ├─ cpu_has_vmx_ept() // cpu是否支持ept | | | ├─ kvm_configure_mmu() // mmu相关硬件判断和全局变量 | | | ├─ cpu_has_vmx_XXX() // cpu是否有XXX | | | ├─ vmx_enable_tdp() // ept支持时开启tdp | | | ├─ kvm_disable_tdp() // 关闭tdp | | | ├─ kvm_set_posted_intr_wakeup_handler() // posted intr wakeup handler | | | └─ alloc_kvm_area() // 给每个cpu分配一个struct vmcs | | └─ kvm_init_msr_list() // 将msr保存到全局变量msrs_to_save[]数组 | ├─ smp_call_function_single() // 对每个online cpu进行兼容性检查 | ├─ cpuhp_setup_state_nocalls() // 注册cpu状态变化的回调函数 | ├─ register_reboot_notifier() // 注册reboot时候的通知函数 | ├─ kvm_cache_create_usercopy() // 创建vcpu 的 kmem cache, 对象大小是sizeof(struct vcpu_vmx) | ├─ kvm_async_pf_init() // 异步 | ├─ misc_register(&kvm_dev) // 注册字符设备文件/dev/kvm | ├─ register_syscore_ops() // 注册系统核心函数, 这里是suspend和resume | ├─ kvm_init_debug() // 初始化debugfs | └─ kvm_vfio_ops_init() // vfio的操作初始化 ├─ vmx_setup_l1d_flush() // └─ vmx_check_vmcs12_offsets() |
vmx.c定义了vmx的架构下的操作函数
1 | r = kvm_init(&vmx_x86_ops, sizeof(struct vcpu_vmx), __alignof__(struct vcpu_vmx), THIS_MODULE); |
正式进行KVM框架初始化
使用传入的
1 | r = kvm_arch_init(opaque); |
主要是架构相关的检查和初始化
1 2 3 4 5 6 | // CPUID.1:ECX.VMX[bit 5] -> VT 必须支持 ops->cpu_has_kvm_support(); // bios必须支持 ops->disable_by_bios(); // FPU和FXSR必须支持 !boot_cpu_has(x86_FEATURE_FPU) || !boot_cpu_has(X86_FEATURE_FXSR); |
1 2 3 4 5 | // arch/x86/kvm/x86.c struct kmem_cache *x86_fpu_cache; EXPORT_SYMBOL_GPL(x86_fpu_cache); x86_fpu_cache = kmem_cache_create("x86_fpu", ...) |
1 2 | static struct kvm_shared_msrs __percpu *shared_msrs; shared_msrs = alloc_percpu(struct kvm_shared_msrs); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // arch/x86/kvm/x86.c kvm_mmu_module_init(); // arch/x86/kvm/mmu.c static struct kmem_cache *pte_list_desc_cache; static struct kmem_cache *mmu_page_header_cache; static struct percpu_counter kvm_total_used_mmu_pages; int kvm_mmu_module_init(void) { kvm_mmu_reset_all_pte_masks(); // cache 初始化 pte_list_desc_cache = kmem_cache_create("pte_list_desc", ...) // cache 初始化 mmu_page_header_cache = kmem_cache_create("kvm_mmu_page_header", ...) // percpu的mmu pages使用计数 percpu_counter_init(&kvm_total_used_mmu_pages, 0, GFP_KERNEL); // 预分配 register_shrinker(&mmu_shrinker); } |
在 arch/x86/kvm/x86.c 中,定义了名为 kvm_x86_ops 的静态变量,通过export_symbol 宏在 全局范围!!! 内导出。
1 2 3 | // arch/x86/kvm/x86.c struct kvm_x86_ops *kvm_x86_ops __read_mostly; EXPORT_SYMBOL_GPL(kvm_x86_ops); |
给全局
1 | kvm_x86_ops = ops; |
1 2 | // 设置MMU的shadow PTE masks kvm_mmu_set_mask_ptes(); |
1 | kvm_timer_init(); |
1 | kvm_lapic_init(); |
1 | kvm_irqfd_init(); |
1 | kvm_arch_hardware_setup(); |
实际上调用的是
1 | kvm_x86_ops->hardware_setup(); |
1 2 3 4 5 6 7 | // arch/x86/kvm/vmx/capabilities.h extern struct vmcs_config vmcs_config; extern struct vmx_capability vmx_capability; // arch/x86/kvm/vmx/vmx.c if (setup_vmcs_config(&vmcs_config, &vmx_capability) < 0) return -EIO; |
这里的这两个全局变量不是指针, 而是真正值, 所以就直接分配了内存
1 | kvm_set_posted_intr_wakeup_handler(wakeup_handler); |
1 | alloc_kvm_area(); |
给每个cpu分配struct vmcs, 用于VMXON region
1 2 3 4 5 6 7 8 9 10 11 12 | struct vmcs_hdr { u32 revision_id:31; u32 shadow_vmcs:1; }; struct vmcs { struct vmcs_hdr hdr; u32 abort; char data[0]; }; DECLARE_PER_CPU(struct vmcs *, current_vmcs); |
1 | kvm_init_msr_list(); |
通过
1 2 3 | for_each_online_cpu(cpu) { smp_call_function_single(cpu, check_processor_compat, &r, 1); } |
对每一个online的cpu调用
在前面的
最终调用
1 2 | r = cpuhp_setup_state_nocalls(CPUHP_AP_KVM_STARTING, "kvm/cpu:starting", kvm_starting_cpu, kvm_dying_cpu); |
当cpu状态变成starting会调用回调函数
- hardware_enable
- hardware_enable_nolock()
- kvm_arch_hardware_enable() // 使能虚拟化特性
- hardware_enable_nolock()
- hardware_disable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | static int kvm_starting_cpu(unsigned int cpu) { raw_spin_lock(&kvm_count_lock); if (kvm_usage_count) hardware_enable_nolock(NULL); raw_spin_unlock(&kvm_count_lock); return 0; } static void hardware_enable_nolock(void *junk) { int cpu = raw_smp_processor_id(); int r; if (cpumask_test_cpu(cpu, cpus_hardware_enabled)) return; cpumask_set_cpu(cpu, cpus_hardware_enabled); r = kvm_arch_hardware_enable(); if (r) { cpumask_clear_cpu(cpu, cpus_hardware_enabled); atomic_inc(&hardware_enable_failed); pr_info("kvm: enabling virtualization on CPU%d failed\n", cpu); } } static int kvm_dying_cpu(unsigned int cpu) { raw_spin_lock(&kvm_count_lock); if (kvm_usage_count) hardware_disable_nolock(NULL); raw_spin_unlock(&kvm_count_lock); return 0; } static void hardware_disable_nolock(void *junk) { int cpu = raw_smp_processor_id(); if (!cpumask_test_cpu(cpu, cpus_hardware_enabled)) return; cpumask_clear_cpu(cpu, cpus_hardware_enabled); kvm_arch_hardware_disable(); } |
原理同上, 重启的时候会调用这个函数
1 | register_reboot_notifier(&kvm_reboot_notifier); |
根据注释, 主要为了满足
1 2 3 4 5 6 7 8 9 | /* A kmem cache lets us meet the alignment requirements of fx_save. */ 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), sizeof_field(struct kvm_vcpu, arch), NULL); |
后面会给创建vcpu时候使用, 该cache的arch部分可被拷贝到用户态使用. 这里的
把早先传给
1 2 3 | kvm_chardev_ops.owner = module; kvm_vm_fops.owner = module; kvm_vcpu_fops.owner = module; |
这三个变量都是
被部分初始化的函数入口地址分别指向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // virt/kvm/kvm_main.c static struct file_operations kvm_vcpu_fops = { .release = kvm_vcpu_release, .unlocked_ioctl = kvm_vcpu_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = kvm_vcpu_compat_ioctl, #endif .mmap = kvm_vcpu_mmap, .llseek = noop_llseek, }; static struct file_operations kvm_vm_fops = { .release = kvm_vm_release, .unlocked_ioctl = kvm_vm_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = kvm_vm_compat_ioctl, #endif .llseek = noop_llseek, }; static struct file_operations kvm_chardev_ops = { .unlocked_ioctl = kvm_dev_ioctl, .compat_ioctl = kvm_dev_ioctl, .llseek = noop_llseek, }; |
1 | misc_register(&kvm_dev); |
下面是设备文件的结构体
1 2 3 4 5 6 7 8 9 10 11 | // virt/kvm/kvm_main.c static struct file_operations kvm_chardev_ops = { .unlocked_ioctl = kvm_dev_ioctl, .llseek = noop_llseek, KVM_COMPAT(kvm_dev_ioctl), }; static struct miscdevice kvm_dev = { KVM_MINOR, "kvm", &kvm_chardev_ops, }; |
1 2 3 4 | register_syscore_ops(&kvm_syscore_ops); kvm_preempt_ops.sched_in = kvm_sched_in; kvm_preempt_ops.sched_out = kvm_sched_out; |
从命名看,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // virt/kvm/kvm_main.c static struct syscore_ops kvm_syscore_ops = { .suspend = kvm_suspend, .resume = kvm_resume, }; // drivers/base/syscore.c static LIST_HEAD(syscore_ops_list); void register_syscore_ops(struct syscore_ops *ops) { mutex_lock(&syscore_ops_lock); list_add_tail(&ops->node, &syscore_ops_list); mutex_unlock(&syscore_ops_lock); } EXPORT_SYMBOL_GPL(register_syscore_ops); |
1 | static __read_mostly struct preempt_ops kvm_preempt_ops; |
从命名看,
1 | kvm_init_debug(); |
kvm模块的加载依赖debugfs,所以在加载之前要手动挂载.
这个函数建立了debugfs目录下的kvm目录,然后建立了一系列复杂的tracepoint
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // virt/kvm/kvm_main.c static int kvm_init_debug(void) { int r = -EEXIST; struct kvm_stats_debugfs_item *p; kvm_debugfs_dir = debugfs_create_dir("kvm", NULL); kvm_debugfs_num_entries = 0; for (p = debugfs_entries; p->name; ++p, kvm_debugfs_num_entries++) { if (!debugfs_create_file(p->name, 0444, kvm_debugfs_dir, (void *)(long)p->offset, stat_fops[p->kind])) goto out_dir; } } |
目前支持的tracepoint有:
1 2 | // arch/x86/kvm/x86.c struct kvm_stats_debugfs_item debugfs_entries[] = { |
1 | kvm_vfio_ops_init(); |