如何阅读KVM代码

来源:互联网 发布:淘宝上算命高人推荐 编辑:程序博客网 时间:2024/04/29 15:47
转载请注明:【转载自博客xelatex KVM】,并附本文链接。谢谢。

KVM作为目前越来越流行的VMM,受到越来越多人的关注。但是关于KVM代码的说明却很少,无论是网上还是实体书籍,都少有这方面的资料。一方面是KVM的代码变动的太快了,另外一方面也是因为这个领域里面的人也不多。我在这里简单写一个《如何阅读KVM代码》,一方面是记录一些信息以备查询,另一方面也是能够抛砖引玉,让刚开始阅读KVM代码的人不用经历无从下手的困境。

文章中采用的内核版本:Linux-3.11,https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.11.tar.gz

一、KVM与Qemu的定位

KVM是一种VMM(Virtual Machine Monitor),是虚拟机管理器;Qemu是一个模拟器,用来模拟不同的机器模型、CPU、存储架构、外设等等。目前的KVM是依赖于Qemu-kvm的项目的,两者的关系对于理解KVM的代码有着非常重要的意义。

首先,总的来说KVM提供虚拟化的机制和解决方案,Qemu提供策略。

Qemu负责几乎所有模拟的工作。对于一个真实的机器来说,BIOS、CPU、内存模型、外存、外设都可能是不同的,Qemu提供了对上述这些的模拟工作。比如内存模型,Qemu在自己的用户态空间申请指定大小的内存,并指定这些内存暴露给Guest VM的情况。如果Guest VM需要一个NUMA的内存模型,Qemu则会申请若干片内存,将其分别作为一个个Node暴露给Guest VM。

KVM负责虚拟化的机制。KVM提供虚拟机管理器相关的一些操作,如虚拟机的创建、销毁、内存区的注册等等。以虚拟机内存模型为例,KVM提供多种内存操作的接口供Qemu调用,但KVM并不提供内存使用的策略,如KVM并维护诸如NUMA等内存模型,而是提供了能够与Qemu userspace相关联的内存结构(memslot),KVM通过软件、硬件机制对内存的访问进行加速。

二、KVM在kernel tree中相关的文件

KVM在内核代码中主要存在如下几个部分:
  • KVM基本的操作在virt/kvm文件夹中
  • KVM体系结构相关的代码在arch/x86/kvm文件夹中
  • KVM基本的头文件在include/linux/kvm*.h和include/linux/asm-generic/kvm*.h中,前者是userspace也会使用的头文件,后者是asm相关的头文件
  • KVM体系结构相关的头文件在arch/x86/include/asm/kvm*.h和arch/x86/include/uapi/asm/kvm*.h中,前者是kernel使用的头文件,后者是kernel和userspace都会使用的头文件

三、KVM的接口

KVM提供的接口中,总的接口在/dev文件系统中的/dev/kvm。该接口提供了KVM最基本的功能,如查询API 版本、创建虚拟机等,对应的设备文件fop结构为:

<virt/kvm/kvm_main.c>
static const struct file_operations kvm_device_fops = {.unlocked_ioctl = kvm_device_ioctl,#ifdef CONFIG_COMPAT.compat_ioctl = kvm_device_ioctl,#endif.release = kvm_device_release,};

该接口主要通过对/dev/kvm的ioctl进行操作,对应的函数为kvm_device_ioctl => kvm_dev_ioctl,该函数是KVM控制的总入口。

KVM在创建虚拟机的过程中,首先通过上述接口创建一个VM(函数kvm_dev_ioctl_create_vm),其中调用了:

<virt/kvm/kvm_main.c>
static int kvm_dev_ioctl_create_vm(unsigned long type){...kvm = kvm_create_vm(type);...r = anon_inode_getfd("kvm-vm", &kvm_vm_fops, kvm, O_RDWR);...}

在调用了kvm_create_vm之后,创建了一个匿名inode,对应的fop为kvm_vm_fops。在Qemu中,则是通过ioctl调用/dev/kvm的接口,返回该inode的文件描述符,之后对该VM的操作全部通过该文件描述符进行,对应的fop结构如下:

<virt/kvm/kvm_main.c>
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.mmap           = kvm_vm_mmap,.llseek= noop_llseek,};

主要的接口是函数kvm_vm_ioctl

创建好了VM之后,对于虚拟机的每个VCPU,Qemu都会为其创建一个线程,在其中调用kvm_vm_ioctl中的KVM_CREATE_VCPU操作创建VCPU,该操作通过调用kvm_vm_ioctl => kvm_vm_ioctl_create_vcpu => create_vcpu_fd创建一个名为"kvm-vcpu"的匿名inode并返回其描述符。之后对每个vcpu的操作都通过该文件描述符进行,该匿名inode的fop如下:

<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,};

对于vcpu的操作主要通过ioctl进行,其最终会调用到kvm_arch_vcpu_ioctl函数中。

下图是我创建一个4核虚拟机得到的结果:


可以看到qemu-system-x86下面有六个子进程,其中有四个vcpu和两个监控线程。我们进入/proc/21815中看一下:


能够看到程序创建的一个名叫"kvm-vm"的anon_inode和四个名叫"kvm-vcpu"的anon_inode。

此外还有一个很重要的控制结构struct kvm_x86_ops,KVM中有一个该结构体声明的同名全局变量,在kvm_arch_init中被置为vmx_x86_ops,该结构体声明如下:

<arch/x86/kvm/vmx.c>
static struct kvm_x86_ops vmx_x86_ops = {.cpu_has_kvm_support = cpu_has_kvm_support,.disabled_by_bios = vmx_disabled_by_bios,.hardware_setup = hardware_setup,.hardware_unsetup = hardware_unsetup,.check_processor_compatibility = vmx_check_processor_compat,.hardware_enable = hardware_enable,.hardware_disable = hardware_disable,.cpu_has_accelerated_tpr = report_flexpriority,.vcpu_create = vmx_create_vcpu,.vcpu_free = vmx_free_vcpu,.vcpu_reset = vmx_vcpu_reset,......};

对于vcpu和vm的很多操作都是通过 kvm_x86_ops->XXXX 的方式来调用的,所有通过kvm_x86_ops来调用的函数,都可以在上述结构体中找到对应的函数。


四、VCPU运行路径

KVM中VCPU运行的主要调用路径是vcpu_enter_guest,在文件arch/x86/kvm/x86.c中。该函数首先准备了vcpu需要运行的各种条件,然后调用:
kvm_x86_ops->run(vcpu);
来让vcpu进入non-root模式。该调用的函数为vmx_vcpu_run,虚拟机进入的点在汇编代码中的:

static void __noclone vmx_vcpu_run(struct kvm_vcpu *vcpu){....../* Enter guest mode */"jne 1f \n\t"__ex(ASM_VMX_VMLAUNCH) "\n\t""jmp 2f \n\t""1: " __ex(ASM_VMX_VMRESUME) "\n\t""2: "......}

标号2的位置,则恰好是虚拟机退出时率先执行的点,并在vcpu_enter_guest函数的最后调用kvm_x86_ops->handle_exit(vcpu)来处理虚拟机的退出, 调用的函数为vmx_handle_exit。vmexit的处理会查找kvm_vmx_exit_handler表,该表对应了不同EXIT REASON的处理函数。

在VMEXIT的处理路径中,还会处理来自kernel或者其它vcpu的请求,该处理通过在vcpu_enter_guest函数中调用kvm_check_request来检查,而kernel或者其它vcpu则可以通过kvm_make_request来设置对应请求位。



至此KVM创建VM、创建VCPU以及VCPU运行路径就基本分析完了。在这个过程中由若干个anon_inode的创建和函数指针的调用,使得这个过程比较难以追踪。明确是上述过程之后,对KVM代码的分析就简单多了。



0 0