Mctrain's Blog

What I learned in IT, as well as thought about life

Qemu中的设备注册

| Comments

这两天在看KVM网络虚拟化相关的内容,找了很多相关的资料,也吭哧吭哧地读了那些简直无法直视的代码,终于感觉渐渐理出点头绪了。之后如果有时间的话会慢慢将这些知识整理出来,当然也包括推荐一些比较靠谱的博客和文档吧。

今天要写的内容是关于“Qemu设备注册”,和KVM网络虚拟化没有太大的关系。其实这篇博文很简单,主要就是解决了一个我之前一直没有搞懂的最基本的问题:

Qemu里面的设备注册函数是如何被调用的?

废话不说,直接进入正篇。

其实在很多设备相关的文件中都会看到代码的最后部分是这样的(比如在virtio的代码中):

hw/virtio/virtio.c
1
type_init(virtio_register_types)

问题是这个type_init是个什么东西?virtio_register_types是如何被调用的?

如果把type_init展开的话:

include/qemu/module.h
1
2
3
4
5
6
7
#define module_init(function, type)                                         \
static void __attribute__((constructor)) do_qemu_init_ ## function(void)    \
{                                                                           \
    register_module_init(function, type);                                   \
}

#define type_init(function) module_init(function, MODULE_INIT_QOM)

可以看到这个函数前面加了一个gcc的attribute:__attribute__((constructor)),这个表示说该函数会在整个程序的main函数执行之前被执行,于是我们继续追踪这个会在main函数执行之前执行的register_module_init:

util/module.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static ModuleTypeList init_type_list[MODULE_INIT_MAX];
...
static ModuleTypeList *find_type(module_init_type type)
{
    ModuleTypeList *l;

    init_lists();

    l = &init_type_list[type];

    return l;
}
...
void register_module_init(void (*fn)(void), module_init_type type)
{
    ModuleEntry *e;
    ModuleTypeList *l;

    e = g_malloc0(sizeof(*e));
    e->init = fn;
    e->type = type;

    l = find_type(type);

    QTAILQ_INSERT_TAIL(l, e, node);
}

可以看到,它把传进来的fn函数指针传递给了一个ModuleEntry变量的init参数,并将其插入一个叫做init_type_list的列表里面。

另外在module_init的时候我们还传了一个参数叫做MODULE_INIT_QOM,这个参数也和init_type_list似乎有着千丝万缕的关系,那么问题来了:

这个MODULE_INIT_QOM又是个什么东西?

好了,实在忍不住就讲了一堆废话,从现在开始不再卖关子了,开始正常地表达人话吧,揭晓答案的一刻开始了:

QOM,全称Qemu Object Model,它是Qemu最新的设备相关的模型,具体可以参看这里这里。纵观Qemu的设备模型发展历史,可以用下面这张图(摘自这里)描述:

qemu device evolution

最开始的Qemu采用的是很混乱的ad hoc模式,每种设备都有不同的表示方式,杂乱无章,于是在2009年Paul Brook一票人实在忍不下去了,开发了QDev的设备模型,将所有的模拟设备进行了整合,变成了一种单根结点(SysBus)的树状形式,并且增加了hotplug的功能。

但是后来由于某些原因(具体是什么我也不是很清楚,似乎是因为QDev的这种单根结点树状组织方式还是不太令人满意,特别是Device和Bus之间缠绕不清的关系),在2012年Anthony Liguori一票人又开发了一套QOM的设备模型,将原来的QDev给换掉了。

QDevQOM的差别主要可以参看这里,从Device RelationshipNamingProperties三个角度来进行区分,我看下来觉得主要就是集中在Bus这么个角色里面,在QDev里面,BusDevice好像是一种平级的关系,而在QOM里面,Bus只是Device的接口。具体的差别这里就不详说了,有兴趣的看之前的那个文档就好了。

总之,现在Qemu采用的是QOM这种设备模型。既然如此,MODULE_INIT_QOM就是用于初始化这些相关的设备咯。

我们可以看到,在Qemu的主函数中,一开始就有这么一句话:

vl.c
1
module_call_init(MODULE_INIT_QOM);
util/module.c
1
2
3
4
5
6
7
8
9
10
11
12
void module_call_init(module_init_type type)
{
    ModuleTypeList *l;
    ModuleEntry *e;

    module_load(type);
    l = find_type(type);

    QTAILQ_FOREACH(e, l, node) {
        e->init();
    }
}

回顾下之前的type_init,也就是说,在main函数开始之前,这些设备会新建一个ModuleEntry,插入init_type_listMODULE_INIT_QOM的项中:

qom init

之后在module_call_init中遍历链表中所有的entry,调用其对应的init函数,比如在我们的例子中,调用virtio_register_types函数:

hw/virtio/virtio.c
1
2
3
4
5
6
7
8
9
10
11
12
13
static const TypeInfo virtio_device_info = {
    .name = TYPE_VIRTIO_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(VirtIODevice),
    .class_init = virtio_device_class_init,
    .abstract = true,
    .class_size = sizeof(VirtioDeviceClass),
};

static void virtio_register_types(void)
{
    type_register_static(&virtio_device_info);
}

virtio_device_info注册到系统中。

以上就是Qemu中的设备如何通过type_initmain函数中的module_call_init进行注册的过程。这里再向大家推荐一个文档:

Qemu设备模拟

这篇文章讲的还蛮清楚的。

至于virtio_device_info中的.class_init等函数何时被调用,则是之后的内容了,等我有心情的时候再写吧。

Comments