接前一篇文章: 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函数将异常终止一个进程,所有的流被关闭和冲洗。
笔者认为,这两个条件未免过于苛刻了(尤其是后一个),应该再松一些,即使值不是那么完全,也能保证程序可以正常运行。
流程中的更多步骤,请看下回。