C程序不全是从main开始的 — 以QEMU中qcow2模块初始化为例

0. GCC的attribute关键字

这是GCC的一个特性,gcc可以使用attribute关键字,格式如下:

__attribute__((attribute_name))

其中attribute_name中有两类constructordestructor类似C++中类的构造和析构的概念,只不过是相对main()函数来说的。简单说,__attribute__((constructor))定义的函数在main前执行,__attribute__((destructor))定义的函数在main后执行。

1. 以qcow2为例

QEMU中有很多”module”的初始化使用了__attribute__((constructor))这个特性,来在main前完成init函数的注册过程。使用方法举例具体如下:

// QEMU_2.10_SRC/include/qemu/module.h中:

#define module_init(function, type)                                         \
static void __attribute__((constructor)) do_qemu_init_ ## function(void)    \
{                                                                           \
    register_module_init(function, type);                                   \
}
#endif

typedef enum {
    MODULE_INIT_BLOCK,
    MODULE_INIT_OPTS,
    MODULE_INIT_QOM,
    MODULE_INIT_TRACE,
    MODULE_INIT_MAX
} module_init_type;
// 比如这里block_init函数被用在QEMU_SRC/block/*的qcow2等Format Driver中广泛应用,
// 其实就是间接调用了被__attribute__((constructor))调用的register_module_init()
#define block_init(function) module_init(function, MODULE_INIT_BLOCK)
#define opts_init(function) module_init(function, MODULE_INIT_OPTS)
#define type_init(function) module_init(function, MODULE_INIT_QOM)
#define trace_init(function) module_init(function, MODULE_INIT_TRACE)

下面以qcow2这个模块的初始化为例介绍下:

Step 1. main函数前执行constructor

第一步利用gcc支持的“构造函数”,将qcow2的初始化函数“注册”:

// qcow2 的init函数的注册流程

block_init(bdrv_qcow2_init)                                              ------------------------ qcow2.c
----+----- -----+---------
    |            |
    |            v
    |        bdrv_register(&bdrv_qcow2)
    v
#define block_init(function) module_init(function, MODULE_INIT_BLOCK)     ---------- include/qemu/module.h
                            ----+-------
                                |
                                v
                            register_module_init(function, type)
                                |
                                v
                            l = find_type(type)         
                            e = g_malloc0(sizeof(*e));  
                            e->init = fn;
                            e->type = type;  
                            QTAILQ_INSERT_TAIL(l, e, node);        ------------------------- util/module.c

util/module.c中有这样一句:static ModuleTypeList init_type_list[MODULE_INIT_MAX];
可以看到在这之后最终函数指针fn通过变量e和宏函数QTAILQ_INSERT_TAIL(l, e, node)被存入事先定义的全局数组init_type_list中的type链表(这里type是MODULE_INIT_BLOCK)中。我们继续看main函数:

Step 2. main函数中进行init

调用流程如下:

  main()                                    ----------------- vl.c
    |
    v
bdrv_init_with_whitelist()                  ----------------- block.c
    |
    v
 bdrv_init()
    |
    v
module_call_init(MODULE_INIT_BLOCK)          ---------------- util/module.c
    |
    v
l = find_type(MODULE_INIT_BLOCK)  -->  QTAILQ_FOREACH(e, l, node){e->init()}

我们看到虽然module_call_init(MODULE_INIT_BLOCK)和上边step 1.中的register_module_init(function, type)函数都在util/module.c中,但是调用的时机是不同的,step1中的register注册模块,step2中的call才是在真正调用init函数,而step1是在main前执行的,step2是在main函数中执行的。


[1] __ATTRIBUTE__ 你知多少?, http://www.cnblogs.com/astwish/articles/3460618.html

[2] QEMU在main函数前对模块的初始化过程, http://blog.csdn.net/u011364612/article/details/53581501

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注