相关参考

KVM概述

image.png
KVM特性

  • KVM基于Intel VT-x或者AMD-V等硬件技术的全虚拟化。
  • KVM是基于Linux内核基础,加了虚拟化的部分管理的模块。
  • KVM由KVM-kernel驱动部分+QEMU-KVM应用部分组成。

内核KVM源码目录及组成

KVM源码目录树分析
内核源码组成

先看下Intel X86的虚拟化部分Makefile

  1. arch/x86/kvm/Makefile
  2. ccflags-y += -Iarch/x86/kvm
  3. KVM := ../../../virt/kvm
  4. kvm-y += $(KVM)/kvm_main.o $(KVM)/coalesced_mmio.o
  5. kvm-y += x86.o emulate.o i8259.o irq.o lapic.o \
  6. i8254.o ioapic.o irq_comm.o cpuid.o pmu.o mtrr.o \
  7. hyperv.o debugfs.o mmu/mmu.o mmu/page_track.o
  8. kvm-intel-y += vmx/vmx.o vmx/vmenter.o vmx/pmu_intel.o vmx/vmcs12.o vmx/evmcs.o vmx/nested.o
  9. obj-$(CONFIG_KVM) += kvm.o
  10. obj-$(CONFIG_KVM_INTEL) += kvm-intel.o

可以看出,Kvm主要由kvm和kvm-intel组成

  1. kvm.o是kvm的核心模块
  • IOMMU、中断控制、设备管理、kvm arch等部分代码
  • kvm并没有完全实现一整个PC系统虚拟化,而仅仅将部分重要的CPU虚拟化、I/O虚拟化和内存虚拟化部分针对硬件辅助的能力进行有效地抽象和对接,其他一些模块需要借助于Qemu
  • 也就是说,kvm基本只实现硬件辅助虚拟化相关部分,而不支持的用Qemu来模拟实现
  1. kvm-intel.o是intel平台架构虚拟化模块,平台相关

    KVM初始化流程

    使用环境:ubuntu 18.04 Desktop,参考 /boot/config-5.4.0-52-generic 配置

    模块初始化:

    image.png
    image.png

整理流程代码概述

  1. // linux/x86/linux-5.7.14/arch/x86/kvm/vmx/vmx.c
  2. static struct kvm_x86_init_ops vmx_init_ops __initdata = {
  3. .cpu_has_kvm_support = cpu_has_kvm_support,
  4. .disabled_by_bios = vmx_disabled_by_bios,
  5. .check_processor_compatibility = vmx_check_processor_compat,
  6. .hardware_setup = hardware_setup,
  7. .runtime_ops = &vmx_x86_ops,
  8. };
  1. // linux/x86/linux-5.7.14/arch/x86/kvm/vmx/vmx.c
  2. vmx_init() // 初始化入口
  3. ├─ kvm_init(KVM_GET_API_VERSION) // 初始化KVM框架
  4. | ├─ kvm_arch_init() // 架构相关初始化
  5. | | ├─ cpu_has_kvm_support() // CPU是否支持kvm
  6. | | ├─ disabled_by_bios() // bios是否禁用vt
  7. | | ├─ boot_cpu_has() // CPU是否支持一些特性
  8. | | ├─ kmem_cache_create("x86_fpu") // x86_fpu kmem_cache
  9. | | ├─ alloc_percpu() // kvm_shared_msrs
  10. | | ├─ kvm_mmu_module_init() // mmu模块初始化
  11. | | ├─ kvm_mmu_set_mask_ptes() // shadow pte mask设置
  12. | | ├─ kvm_timer_init() // 时钟初始化
  13. | | ├─ kvm_lapic_init() // lapic初始化
  14. | ├─ kvm_irqfd_init() //
  15. | ├─ kvm_arch_hardware_setup() //
  16. | | ├─ kvm_x86_ops->hardware_setup() // CPU是否支持kvm
  17. | | | ├─ rdmsrl_safe() // 读msr
  18. | | | ├─ store_idt() // 保存idt
  19. | | | ├─ setup_vmcs_config() // 建立vmcs_config和vmx_capability
  20. | | | ├─ boot_cpu_has() // CPU特性支持
  21. | | | ├─ cpu_has_vmx_vpid() // cpu是否支持vpid
  22. | | | ├─ cpu_has_vmx_invvpid() // cpu是否支持invvpid
  23. | | | ├─ cpu_has_vmx_ept() // cpu是否支持ept
  24. | | | ├─ kvm_configure_mmu() // mmu相关硬件判断和全局变量
  25. | | | ├─ cpu_has_vmx_XXX() // cpu是否有XXX
  26. | | | ├─ vmx_enable_tdp() // ept支持时开启tdp
  27. | | | ├─ kvm_disable_tdp() // 关闭tdp
  28. | | | ├─ kvm_set_posted_intr_wakeup_handler() // posted intr wakeup handler
  29. | | | └─ alloc_kvm_area() // 给每个cpu分配一个struct vmcs
  30. | | └─ kvm_init_msr_list() // 将msr保存到全局变量msrs_to_save[]数组
  31. | ├─ smp_call_function_single() // 对每个online cpu进行兼容性检查
  32. | ├─ cpuhp_setup_state_nocalls() // 注册cpu状态变化的回调函数
  33. | ├─ register_reboot_notifier() // 注册reboot时候的通知函数
  34. | ├─ kvm_cache_create_usercopy() // 创建vcpu 的 kmem cache, 对象大小是sizeof(struct vcpu_vmx)
  35. | ├─ kvm_async_pf_init() // 异步
  36. | ├─ misc_register(&kvm_dev) // 注册字符设备文件/dev/kvm
  37. | ├─ register_syscore_ops() // 注册系统核心函数, 这里是suspend和resume
  38. | ├─ kvm_init_debug() // 初始化debugfs
  39. | └─ kvm_vfio_ops_init() // vfio的操作初始化
  40. ├─ vmx_setup_l1d_flush() //
  41. └─ vmx_check_vmcs12_offsets()

初始化流程-我们先大概浏览下框架**
  1. vmx_init // vmx/vmx.c
  2. // IS_ENABLED(CONFIG_HYPERV) 这里不考虑,HYPER-Vwindows的虚拟化产品
  3. kvm_init // virt/kvm_main.c
  4. kvm_arch_init // kvm/x86/x86.c 硬件架构相关初始化,判断硬件支持能力
  5. kvm_irqfd_init // 创建kvm-irqfd-cleanup 工作队列
  6. zalloc_cpumask_var
  7. kvm_arch_hardware_setup
  8. for_each_online_cpu(cpu)
  9. smp_call_function_single(cpu, check_processor_compat, &c, 1);
  10. cpuhp_setup_state_nocalls
  11. register_reboot_notifier
  12. kvm_async_pf_init
  13. misc_register(&kvm_dev); // qemu对用户层接口:/dev/kvm接口注册-使用misc混杂设备
  14. register_syscore_ops(&kvm_syscore_ops);
  15. kvm_init_debug(); // debugfs init创建
  16. kvm_vfio_ops_init();
  17. .....

初始化-kvm_arch_init

  1. kvm_arch_init() // 这里操作接口传入 vmx_init_ops
  2. /******* 第一部分:KVM的硬件环境检测 ********/
  3. kvm_x86_ops.hardware_enable // 这里用来判断是否使能过-没使能,否则重复加载错误
  4. // cpu_has_vmx 汇编读取CPU-ID,判断CPU硬件是否支持虚拟化
  5. ops->cpu_has_kvm_support()
  6. // vmx_disabled_by_bios 读取BIOS是否禁止了 vmx
  7. ops->disabled_by_bios()
  8. // kvm明确规定guest是需要FPU浮点支持和FXSAVE/FXRSTOR指令支持
  9. !boot_cpu_has(X86_FEATURE_FPU) || !boot_cpu_has(X86_FEATURE_FXSR)
  10. /******** 第二部分:KVM资源分配 **********/
  11. // __alignof__(struct fpu) 判断结构体内部是多少字节对齐
  12. x86_fpu_cache = kmem_cache_create // 创建SLAB高速缓存 struct fpu
  13. x86_emulator_cache = kvm_alloc_emulator_cache // emulator-仿真,看名称是创建仿真高速缓存
  14. shared_msrs = alloc_percpu(struct kvm_shared_msrs); // 给每个CPU都有一个msrs变量
  15. /******** 第三部分:MMU相关 **********/
  16. kvm_mmu_module_init
  17. __set_nx_huge_pages(get_nx_auto_mode()); // 不研究
  18. kvm_mmu_reset_all_pte_masks(); //
  19. kvm_set_mmio_spte_mask();
  20. kvm_mmu_set_mask_ptes
  21. /******** 第四部分:混杂项 **********/
  22. kvm_timer_init

初始化-kvm_arch_hardware_setup

  1. kvm_arch_hardware_setup
  2. dmsrl_safe(MSR_EFER, &host_efer);
  3. ops->hardware_setup(); // X86的 hardware_setup 函数
  4. memcpy(&kvm_x86_ops, ops->runtime_ops, sizeof(kvm_x86_ops)); // 这里有个run time ops

kvm_get_shadow_phys_bits

  1. / *
  2. *当在CPU检测代码中检测到MKTMESME时,boot_cpu_data.x86_phys_bits会减少,
  3. 但是处理器会将这些减少的位视为“ keyID”,因此它们不是保留位。
  4. 因此,KVM需要查看CPUID报告的物理地址位。
  5. * /
  6. if (likely(boot_cpu_data.extended_cpuid_level >= 0x80000008))
  7. return cpuid_eax(0x80000008) & 0xff;
  8. / *
  9. *非常奇怪有VMXSVM但没有MAXPHYADDR 可能是具有自定义CPUIDVM 继续执行发现的所有内核,因为这些功能不可虚拟化(SME / SEV还需要大于0x80000008CPUID)。
  10. * /
  11. return boot_cpu_data.x86_phys_bits;
  12. / *

QEMU-KVM创建虚拟机

  1. kvm_dev_ioctl
  2. kvm_dev_ioctl_create_vm(arg); // case KVM_CREATE_VM:
  3. kvm_create_vm(type);
  4. kvm_coalesced_mmio_init(kvm);
  5. get_unused_fd_flags(O_CLOEXEC);
  6. anon_inode_getfile("kvm-vm", &kvm_vm_fops, kvm, O_RDWR);
  7. kvm_create_vm_debugfs(kvm, r)
  8. kvm_uevent_notify_change(KVM_EVENT_CREATE_VM, kvm);
  9. fd_install(r, file);

qemu 源码分析

在《qemu调试系统》中描述了如何创建一个X86的虚拟机,这里简要贴下如何编译qemu和使用qemu:

  1. cd qemu
  2. ./configure --prefix=/opt/x86_64/qemu-x86-bin --target-list=x86_64-softmmu --gdb=/usr/bin/gdb \
  3. --enable-linux-aio --enable-debug --enable-debug-info
  4. sudo make && sudo make install
  5. sudo echo "PATH=\$PATH:/usr/local/qemu_x86/bin" >> ~/.bashrc
  6. source ~/.bashrc
  7. which qemu-system-x86_64
  8. /usr/local/qemu_x86/bin/qemu-system-x86_64 \
  9. -smp 2 \
  10. -cpu host \
  11. -enable-kvm \
  12. -m 512M \
  13. -kernel linux/arch/x86/boot/bzImage \
  14. -hda ./x86_64.img \
  15. -hdb ./Freeze.img \
  16. -nographic \
  17. -append "root=/dev/sda rw rootfstype=ext4 console=ttyS0 init=linuxrc loglevel=8"

驱动初始化函数

QEMU初始化代码并不是在main入口函数中执行,而是有一部分驱动初始化模块:

  1. #define module_init(function, type) \
  2. static void __attribute__((constructor)) do_qemu_init_ ## function(void) \
  3. { \
  4. register_module_init(function, type); \
  5. }
  6. #define block_init(function) module_init(function, MODULE_INIT_BLOCK)
  7. #define opts_init(function) module_init(function, MODULE_INIT_OPTS)
  8. #define type_init(function) module_init(function, MODULE_INIT_QOM)
  9. #define trace_init(function) module_init(function, MODULE_INIT_TRACE)
  10. #define xen_backend_init(function) module_init(function, \
  11. MODULE_INIT_XEN_BACKEND)
  12. #define libqos_init(function) module_init(function, MODULE_INIT_LIBQOS)
  13. #define fuzz_target_init(function) module_init(function, \
  14. MODULE_INIT_FUZZ_TARGET)
  15. #define migration_init(function) module_init(function, MODULE_INIT_MIGRATION)

可以看到,attribute((constructor))用法解析 中讲述了: constructor 修饰的属性,在main函数之前执行
会将所有相同的驱动模块加入到同一个类型的链表中。 10.KVM源码 - 图410.KVM源码 - 图5所以软件中 module_call_init(MODULE_INIT_XXX) 会遍历当前节点的所有设备进行初始化

入口函数总览

10.KVM源码 - 图6

qemu_init初始化函数

10.KVM源码 - 图7

module_call_init

在 驱动初始化函数 部分描述了,系统上电后,会将所有的设备注册到对应类型的链表中,这个接口是调用了当前接口类型下所有设备的init函数 进行初始化。

qemu_add_opts和qemu_add_drive_opts

  1. static QemuOptsList *vm_config_groups[48];
  2. static QemuOptsList *drive_config_groups[5];

添加 操作到对应的配置或驱动组 模块

argv_parse

这个函数最终会解析:qemu-options.def 中的结构体信息进行描述,这里在编译时会打包到
static const QEMUOption qemu_options[]中;

  1. const QEMUOption *popt; popt = lookup_opt(argc, argv, &optarg, &optind);
  2. popt = qemu_options; // 这个函数会从这个全局变量里边查找*****
  3. typedef struct QEMUOption {
  4. const char *name;
  5. int flags;
  6. int index;
  7. uint32_t arch_mask;
  8. } QEMUOption;
  9. static const QEMUOption qemu_options[] = {
  10. { "h", 0, QEMU_OPTION_h, QEMU_ARCH_ALL },
  11. #define QEMU_OPTIONS_GENERATE_OPTIONS
  12. #include "qemu-options-wrapper.h"
  13. { NULL },
  14. };
  15. #define DEF(option, opt_arg, opt_enum, opt_help, arch_mask) \
  16. { option, opt_arg, opt_enum, arch_mask },
  17. #define DEFHEADING(text)
  18. #define ARCHHEADING(text, arch_mask)
  19. #include "qemu-options.def"