您的位置:首页 > 产品中心

QEMU之CPU虚拟化(二):KVM模块初始化介绍

来源:米乐体育赛事    发布时间:2023-10-04 08:52:22

  最近在阅读李强编著的《QEMU/KVM源码解析与应用》这本书来学习Linux内核虚拟化相关知识,通过读书笔记的方式来提炼和归纳书中重要的知识点。本系列主要内容是关于QEMU中CPU虚拟化方面的介绍。

  KVM是一种基于内核的虚拟机监控器,其架构简单清晰,充分复用了Linux内核的诸多功能。本文将对KVM模块的初始化流程进行介绍。

  KVM在Linux内核树中的代码组织最重要的包含通用部分代码和架构相关代码这两部分。

  KVM本质上是一个虚拟化的统称方案,当前主流的处理器架构,包括x86,ARM和RISCV等都有自己的虚拟化架构实现方案,而KVM作为抽象层,屏蔽了底层虚拟化架构实现的差异,为用户态程序(主要是QEMU)提供了统一的接口。

  KVM的主体代码位于内核树virt/kvm目录下面,表示所有CPU架构的公共代码,这也是kvm.ko对应的源码。

  CPU架构代码位于arch/目录下面,例如x86的架构相关代码在arch/x86/kvm下。进一步地,同一个架构可能会出现多种不同的实现,例如x86架构下就有Intel和AMD两家的CPU实现,所以在x86目录下面就有多种实现代码:

  此外,CPU架构代码下还包括了像中断控制器(ioapic.c和lapic.c)、性能监控单元(pmu.c)以及CPUID(cpuid.c)等虚拟化代码。

  题外话,熟悉Linux内核的开发者应该能立刻发现,这种源码组织架构也常见于Linux内核中的其他子系统。

  KVM的所有虚拟化实现(Intel和AMD)都会向KVM模块注册一个kvm_x86_ops结构体,这样KVM中的一些函数就仅仅作为一个外壳,它可能首先会调用kvm_arch_xxx函数,表示的是调用CPU架构相关的函数,而如果kvm_arch_xxx函数需要调用到实现相关的代码,则会调用kvm_x86_ops结构中的相关回调函数。

  KVM的通用部分和架构相关部分代码都单独编译成Linux内核模块,因此在使用的时候也需要一起进行加载,在Intel平台上就是kvm.ko和kvm-intel.ko两个内核模块。

  KVM初始化完成后,会向用户空间呈现KVM的接口(即前面讲的虚拟化统一接口),这些接口都是由kvm.ko导出的,当用户程序调用这些接口时,kvm.ko中的通用代码反过来会调用kvm-intel.ko架构中的相关代码。调用关系如下所示:

  KVM模块的初始化最重要的包含初始化CPU与架构无关的数据及设置与架构相关的虚拟化支持。

  在Intel平台上,VMM只有在CPU处于保护模式并且开启分页时才能进入VMX模式。开启VMX模式的步骤简单概括如下:

  检测CPU支持的VMX能力,通过读取与VMX能力相关的MSR寄存器完成;

  确保IA32_FEATURE_CONTROL寄存器被正确设置,其锁定位(0位)为1,这个MSR寄存器通常由BIOS编程。

  使用VMXON区域的物理地址作为操作数调用VMXON指令,执行完成后,如果RFLAGS.CF=0则表示VMXON指令执行成功。

  KVM的初始化流程在kvm-intel.ko的模块注册函数vmx_init中完成。下面以最新的Linux内核v6.5为例进行代码分析。

  kvm_is_vmx_supported:检测是否支持并且已经开启了VMX模式,否则后面的初始化流程就毫无意义了;

  kvm_x86_vendor_init函数在获取了相关的锁之后,最终调用__kvm_x86_vendor_init来完成实际的初始化流程,后者的代码去掉一些注释、不重要的过程以及错误处理路径之后,其主要的流程如下代码所示:

  kvm_mmu_vendor_module_init:完成内存虚拟化中跟MMU架构相关部分的初始化,但大部分的初始化过程将被推迟到供应商模块(kvm-intel.ko或者kvm-amd.ko)加载的时候完成,因为许多掩码/值会被VMX或者SVM修改;

  ops-hardware_setup:用来创建一些跟启动KVM紧密关联的数据结构和初始化一些硬件特性,里面涵盖的内容比较多,这中间还包括MMU和扩展页表(EPT)的设置和初始化、嵌套虚拟化的配置以及CPU支持特性列表的设置等等,具体的可以直接去查看arch/x86/kvm/vmx/vmx.c#hardware_setup(void)方法;

  kvm_init将完成KVM通用部分的初始化,该过程完成后,KVM模块就将/dev/kvm暴露给了用户态,作为用户态程序(QEMU)与KVM模块通信的接口。

  首先调用setup_vmcs_config用于设置一个全局变量vmcs_config,该函数根据查看CS的特性支持情况来填写vmcs_config(对应开启条件2),之后在创建虚拟CPU的时候用这个配置来初始化VMCS。

  kvm_init的最后一个重要工作是创建一个misc设备/dev/kvm,该设备的定义及其对应的操作如下:

  能够正常的看到,该设备只支持ioctl系统调用,当然,open和close这些系统调用会被misc设备框架处理。

  这就是kvm_init的主要工作。可以看到,KVM模块的初始化过程主要是对硬件进行全方位检查,分配一些常用结构的缓存,创建一个/dev/kvm设备,得到vmcs的一个配置结构vmcs_config,并根据CPU特性设置一些全局变量,给每个物理CPU分配一个vmcs结构。

  值得注意的是,这样一个时间段CPU还不在VMX模式下,因为在vmx_init初始化的过程中并没有向CR4.VMXE写入1,也没有分配VMXON区域。这实际上也是一种惰性策略,毕竟如果加载了KVM模块,却一个虚拟机也不创建,那也就没有必要让CPU进入VMX模式。所以VMX模式的真正开启是在创建第一个虚拟机的时候。

推荐资讯