QEMU源码全解析 —— 块设备虚拟化(10)

接前一篇文章: QEMU源码全解析 —— 块设备虚拟化(9)

本文内容参考:

《趣谈 Linux操作系统 》 —— 刘超,极客时间

QEMU /KVM源码解析与应用》 —— 李强,机械工业出版社

特此致谢!

QEMU初始化阶段的块设备虚拟化

从模板生成类和类的实例化

本回继续解析QEMU类Java反射机制的整个流程。

在QEMU中,每一个Module回模拟某种设备,那么就定义一种类型TypeImpl来表示此种设备。

完整的函数调用链为:

type_init()

---> type_register_static()

---> type_register()

---> type_register_internal()

---> type_new()

---> type_table_add()

在type_register_internal函数(qom/object.c中)中,会根据virtio_blk_info这个TypeInfo,创建一个TypeImpl,来表示这个新注册的类。也就是说,TypeImpl才是真正想要声明的那个类。

在QEMU中,有一个全局的哈希表type_table,用来存放所有定义的类。在type_register_internal函数中,先调用type_new函数,从全局表里头根据名称(本例中是TypeInfo virtio_blk_info)查找这个类。如果找到,则说明该类曾经被注册过,就报错;如果没有找到,则说明此类是一个新的类,那么就将TypeInfo中的信息填到TypeImpl里面。

上一回没有对type_new函数进行详解,本回详细看一下。type_new函数也在qom/object.c中,代码如下:

static TypeImpl *type_new(const TypeInfo *info)
{
    TypeImpl *ti = g_malloc0(sizeof(*ti));
    int i;

    g_assert(info->name != NULL);

    if (type_table_lookup(info->name) != NULL) {
        fprintf(stderr, "Registering `%s' which already exists\n", info->name);
        abort();
    }

    ti->name = g_strdup(info->name);
    ti->parent = g_strdup(info->parent);

    ti->class_size = info->class_size;
    ti->instance_size = info->instance_size;
    ti->instance_align = info->instance_align;

    ti->class_init = info->class_init;
    ti->class_base_init = info->class_base_init;
    ti->class_data = info->class_data;

    ti->instance_init = info->instance_init;
    ti->instance_post_init = info->instance_post_init;
    ti->instance_finalize = info->instance_finalize;

    ti->abstract = info->abstract;

    for (i = 0; info->interfaces && info->interfaces[i].type; i++) {
        ti->interfaces[i].typename = g_strdup(info->interfaces[i].type);
    }
    ti->num_interfaces = i;

    return ti;
}

type_new函数一上来先分配一个sizeof(TypeImpl)大小的空间,使TyptImpl *ti指向这个空间。代码片段如下:

    TypeImpl *ti = g_malloc0(sizeof(*ti));

之后要求确保传入的函数参数TypeInfo *info中的name成员不能为空。代码片段如下:

    g_assert(info->name != NULL);

这个条件一般都能够满足。比如virtio_blk_info中的name成员为TYPE_VIRTIO_BLK,即"virtio-blk-device"。

static const TypeInfo virtio_blk_info = {
    .name = TYPE_VIRTIO_BLK,
    .parent = TYPE_VIRTIO_DEVICE,
    .instance_size = sizeof(VirtIOBlock),
    .instance_init = virtio_blk_instance_init,
    .class_init = virtio_blk_class_init,
};
 
static void virtio_register_types(void)
{
    type_register_static(&virtio_blk_info);
}
 
type_init(virtio_register_types)

接下来就来到了type_new函数最核心的一段代码:

    if (type_table_lookup(info->name) != NULL) {
        fprintf(stderr, "Registering `%s' which already exists\n", info->name);
        abort();
    }

这就是上边讲到的:

先调用type_new函数,从全局表里头根据名称(本例中是TypeInfo virtio_blk_info)查找这个类。如果找到,则说明该类曾经被注册过,就报错;

type_table_lookup函数也在qom/object.c中(就在type_new函数上边),代码如下:

static TypeImpl *type_table_lookup(const char *name)
{
    return g_hash_table_lookup(type_table_get(), name);
}

type_table_get函数也在qom/object.c中,代码如下:

static GHashTable *type_table_get(void)
{
    static GHashTable *type_table;

    if (type_table == NULL) {
        type_table = g_hash_table_new(g_str_hash, g_str_equal);
    }

    return type_table;
}

这个GHashTable *type_table就是上边提到的全局(哈希)表。

接下来的代码片段:

    ti->name = g_strdup(info->name);
    ti->parent = g_strdup(info->parent);

    ti->class_size = info->class_size;
    ti->instance_size = info->instance_size;
    ti->instance_align = info->instance_align;

    ti->class_init = info->class_init;
    ti->class_base_init = info->class_base_init;
    ti->class_data = info->class_data;

    ti->instance_init = info->instance_init;
    ti->instance_post_init = info->instance_post_init;
    ti->instance_finalize = info->instance_finalize;

    ti->abstract = info->abstract;

    for (i = 0; info->interfaces && info->interfaces[i].type; i++) {
        ti->interfaces[i].typename = g_strdup(info->interfaces[i].type);
    }
    ti->num_interfaces = i;

    return ti;

就对应上边讲到的:

如果没有找到,则说明此类是一个新的类,那么就将TypeInfo中的信息填到TypeImpl里面。

至此,type_new函数就解析完了。但是,这里边笔者觉得有明显不足:

函数中先后调用了g_assert函数和abort函数。g_assert函数基本上和assert函数一样,首先向标准错误流stderr打印一条出错信息,然后再通过调用abort函数终止程序运行;而abort函数将异常终止一个进程,所有的流被关闭和冲洗。

笔者认为,这两个条件未免过于苛刻了(尤其是后一个),应该再松一些,即使值不是那么完全,也能保证程序可以正常运行。

流程中的更多步骤,请看下回。