/dev/kvm是个字符设备文件,在虚拟化中主要作用就是对应用程序即qemu提供接口,这个接口是通过系统调用ioctl进行访问的。在上一篇文章KVM模块初始化源码分析我们看到在kvm模块初始化的时候会创建misc设置/dev/kvm,设备文件遵循通用文件操作类型即file_operations
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,
};
这样内核就对外提供了针对/dev/kvm文件的操作接口,并通过系统调用ioctl进行操作。具体系统调用执行的函数就是kvm_dev_ioctl,这里不贴源码了,源码位置在virt/kvm/kvm_main.c文件中,读者自行去查看。
那么创建虚拟机的时候是怎么访问/dev/kvm的呢,在什么时候访问的呢?
一般我们采用倒追的方式去分析问题,在kvm_dev_ioctl函数中有一个case KVM_CREATE_VM的分支情况,通过名字我们可以知道这里是创建一台虚拟机,参数是通过ioctl系统调用创建来的,并且其中一个参数名ioctl的值就是KVM_CREATE_VM。即应用程序通过ioctl调用传进来的参数中,其中一个是KVM_CREATE_VM,当然这是对开启了虚拟化加速的才会传入这个参数。是哪一个应用程序会调用ioctl,在以前的文章中我们知道,是qemu应用程序通过系统调用ioctl传入的。
OK,我们通过grep qemu的代码发现在accel/kvm/kvm-all.c文件中有对ioctl的系统调用,并且传入的参数中有KVM_CREATE_VM,查看其源码如下
do {
ret = kvm_ioctl(s, KVM_CREATE_VM, type);
} while (ret == -EINTR);
if (ret < 0) {
fprintf(stderr, "ioctl(KVM_CREATE_VM) failed: %d %s\n", -ret,
strerror(-ret));
int kvm_ioctl(KVMState *s, int type, ...)
{
int ret;
void *arg;
va_list ap;
va_start(ap, type);
arg = va_arg(ap, void *);
va_end(ap);
trace_kvm_ioctl(type, arg);
ret = ioctl(s->fd, type, arg);
if (ret == -1) {
ret = -errno;
}
return ret;
}
kvm_ioctl的调用者是kvm_init函数,而kvm_init函数在进行类型的注册和初始化的时候会被类型AccelClass->init_machine函数指针指向。
static void kvm_accel_class_init(ObjectClass *oc, void *data)
{
AccelClass *ac = ACCEL_CLASS(oc);
ac->name = "KVM";
ac->init_machine = kvm_init;
ac->has_memory = kvm_accel_has_memory;
ac->allowed = &kvm_allowed;
object_class_property_add(oc, "kernel-irqchip", "on|off|split",
NULL, kvm_set_kernel_irqchip,
NULL, NULL);
object_class_property_set_description(oc, "kernel-irqchip",
"Configure KVM in-kernel irqchip");
object_class_property_add(oc, "kvm-shadow-mem", "int",
kvm_get_kvm_shadow_mem, kvm_set_kvm_shadow_mem,
NULL, NULL);
object_class_property_set_description(oc, "kvm-shadow-mem",
"KVM shadow MMU size");
}
static const TypeInfo kvm_accel_type = {
.name = TYPE_KVM_ACCEL,
.parent = TYPE_ACCEL,
.instance_init = kvm_accel_instance_init,
.class_init = kvm_accel_class_init,
.instance_size = sizeof(KVMState),
};
static void kvm_type_init(void)
{
type_register_static(&kvm_accel_type);
}
type_init(kvm_type_init);
那继续看AccelClass->init_machine函数指针会被谁调用。同样搜索发现在文件accel/accel.c中会调用该函数
int accel_init_machine(AccelState *accel, MachineState *ms)
{
AccelClass *acc = ACCEL_GET_CLASS(accel);
int ret;
ms->accelerator = accel;
*(acc->allowed) = true;
ret = acc->init_machine(ms);
if (ret < 0) {
ms->accelerator = NULL;
*(acc->allowed) = false;
object_unref(OBJECT(accel));
} else {
object_set_accelerator_compat_props(acc->compat_props);
}
return ret;
}
accel_init_machine函数又会被softmmu/vl.c文件的do_configure_accelerator函数所调用。
if (!qemu_opts_foreach(qemu_find_opts("accel"),
do_configure_accelerator, &init_failed, &error_fatal)) {
if (!init_failed) {
error_report("no accelerator found");
}
exit(1);
}
do_configure_accelerator函数又被configure_accelerators函数调用,而configure_accelerators则是被qemu_init函数调用,这也是qemu的main函数调用的地方。
那现在就清楚了ioctl(/dev/kvm)的调用逻辑了,我们通过流程图梳理一下