本文展示的代码中,qemu版本为1.6.0, Linux内核版本为3.12.73

为了介绍qemu和kvm的交互过程,我首先介绍一下kvm给用户提供的接口。kvm是一个内核模块,它实现了一个/dev/kvm的字符设备来与用户进行交互,通过调用一系列ioctl函数可以实现qemu和kvm之间的切换。当要创建一个新的虚拟机时,首先打开/dev/kvm设备,在其上调用ioctl函数:

1
2
system_fd = open("/dev/kvm", ORDWR);
vm_fd = ioctl(system_fd, KVM_CREATE_VM, 0);

ioctl函数在kvm中的实现为virt/kvm/kvm_main.c中kvm_dev_ioctl函数,当传入的参数为KVM_CREATE_VM时,该函数会创建一个VM,并且返回一个fd,通过该fd可以操作虚拟机。

创建完虚拟机之后,需要在该虚拟机上面创建vcpu,调用的接口也是ioctl,只是此时对应的fd为创建虚拟机时返回的fd。

1
vcpu_fd = ioctl(vm_fd, VM_CREATE_VCPU, 0);

此时ioctl函数对应的实现为virt/kvm/kvm_main.c中kvm_vm_ioctl函数,当传入的参数为VM_CREATE_VCPU时,与KVM_CREATE_VM过程类似,它创建一个vcpu并且返回可以操作该vcpu的fd。

创建完vcpu后,可以在该vcpu上面调用ioctl函数进入guest vm。

1
ret = ioctl(vcpu_fd, KVM_RUN, 0);

此时ioctl函数对应的实现为virt/kvm/kvm_main.c中kvm_vcpu_ioctl函数,若传入的参数为KVM_RUN,它最终会调用vcpu_enter_guest函数进入guest vm。

qemu作为一个user mode的程序,其入口为main函数,该main函数定义在vl.c文件中。main函数比较长,其中跟KVM初始化相关的主要有两个函数:configure_accelerator()和machine->init(&args)。cofigure_accelerator()函数选择运用哪一种虚拟化方案,其应用到的数据结构为accel_list,会调用accel_list[i].init函数。accel_list的初始化如下所示,当使用kvm虚拟化解决方案时,accel_list[i].init对应的函数即为kvm_init。

1
2
3
4
5
6
7
8
9
10
11
12
static struct {
const char *opt_name;
const char *name;
int (*available)(void);
int (*init)(void);
bool *allowed;
} accel_list[] = {
{ "tcg", "tcg", tcg_available, tcg_init, &tcg_allowed },
{ "xen", "Xen", xen_available, xen_init, &xen_allowed },
{ "kvm", "KVM", kvm_available, kvm_init, &kvm_allowed },
{ "qtest", "QTest", qtest_available, qtest_init, &qtest_allowed },
};

kvm_init函数定义在kvm-all.c文件中,其主要功能是打开/dev/kvm设备,创建一个虚拟机。

machine->init(&arg)函数主要初始化硬件设备,并且调用qemu_init_vcpu为每一个vcpu创建一个线程,线程执行的函数为qemu_kvm_cpu_thread_fn。
qemu_kvm_cpu_thread_fn函数创建vcpu,然后调用kvm_cpu_exec函数。kvm_cpu_exec函数调用ioctl进入kvm并最终进入guest vm。


参考资料:

  1. csdn dashulu
  2. csdn Lux_Veritas