聊聊/dev/kvm的调用逻辑

聊聊/dev/kvm的调用逻辑

Posted by lwk on May 20, 2022

/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)的调用逻辑了,我们通过流程图梳理一下

image