接前一篇文章:QEMU源码全解析40 —— Machine(10)
本文内容参考:
《趣谈Linux操作系统》 —— 刘超,极客时间
《QEMU/KVM》源码解析与应用 —— 李强,机械工业出版社
特此致谢!
时间过去了几个月,重开“Machine”系列……
“Machine”系列的前10篇文章、尤其是从第3篇文章开始,就感觉比较乱,一直在针对于宏定义进行展开,并且头绪众多,给人感觉没有一个清晰的脉络。那么本篇文章就来总结前边几篇文章的内容,并梳理出较为清晰的脉络。
先来看一张图(图片援引《趣谈Linux系统》50 | 计算虚拟化之CPU(上):如何复用集团的人力资源?):
在hw/i386/pc_piix.c中,有核心宏DEFINE_I440FX_MACHINE。对于每一个QEMU版本,都会定义一种新的机器类型,以笔者之前的v7.1以及现在使用的v8.1为例,代码分别如下:
DEFINE_I440FX_MACHINE(v7_1, "pc-i440fx-7.1", NULL,
pc_i440fx_7_1_machine_options);
DEFINE_I440FX_MACHINE(v8_1, "pc-i440fx-8.1", NULL,
pc_i440fx_8_1_machine_options);
DEFINE_I440FX_MACHINE宏的定义也在hw/i386/pc_piix.c中,如下:
#define DEFINE_I440FX_MACHINE(suffix, name, compatfn, optionfn) \
static void pc_init_##suffix(MachineState *machine) \
{ \
void (*compat)(MachineState *m) = (compatfn); \
if (compat) { \
compat(machine); \
} \
pc_init1(machine, TYPE_I440FX_PCI_HOST_BRIDGE, \
TYPE_I440FX_PCI_DEVICE); \
} \
DEFINE_PC_MACHINE(suffix, name, pc_init_##suffix, optionfn)
对于v7.1和v8.1,最终展开为:
static void pc_init_v7_1(MachineState *machine)
{
void (*compat)(MachineState *m) = (NULL);
if (compat) {
compat(machine);
}
pc_init1(machine, TYPE_I440FX_PCI_HOST_BRIDGE, \
TYPE_I440FX_PCI_DEVICE);
}
static void pc_machine_v7_1_class_init(ObjectClass *oc, void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
pc_i440fx_7_1_machine_options(mc);
mc->init = pc_init_v7_1;
}
static const TypeInfo pc_machine_type_v7_1 = {
.name = "pc-i440fx-7.1" TYPE_MACHINE_SUFFIX,
.parent = TYPE_PC_MACHINE,
.class_init = pc_machine_v7_1_class_init,
};
static void pc_machine_init_v7_1(void)
{
type_register(&pc_machine_type_v7_1);
}
type_init(pc_machine_init_v7_1)
和
static void pc_init_v8_1(MachineState *machine)
{
void (*compat)(MachineState *m) = (NULL);
if (compat) {
compat(machine);
}
pc_init1(machine, TYPE_I440FX_PCI_HOST_BRIDGE, \
TYPE_I440FX_PCI_DEVICE);
}
static void pc_machine_v8_1_class_init(ObjectClass *oc, void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
pc_i440fx_8_1_machine_options(mc);
mc->init = pc_init_v8_1;
}
static const TypeInfo pc_machine_type_v8_1 = {
.name = "pc-i440fx-8.1" TYPE_MACHINE_SUFFIX,
.parent = TYPE_PC_MACHINE,
.class_init = pc_machine_v8_1_class_init,
};
static void pc_machine_init_v8_1(void)
{
type_register(&pc_machine_type_v8_1);
}
type_init(pc_machine_init_v8_1)
以v8.1为例,将上述代码分为四段,一一与上图对应起来:
- pc_machine_type_v8_1
static const TypeInfo pc_machine_type_v8_1 = {
.name = "pc-i440fx-8.1" TYPE_MACHINE_SUFFIX,
.parent = TYPE_PC_MACHINE,
.class_init = pc_machine_v8_1_class_init,
};
对应
- pc_machine_v8_1_class_init
static void pc_machine_v8_1_class_init(ObjectClass *oc, void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
pc_i440fx_8_1_machine_options(mc);
mc->init = pc_init_v8_1;
}
对应
- pc_init_v8_1
static void pc_init_v8_1(MachineState *machine)
{
void (*compat)(MachineState *m) = (NULL);
if (compat) {
compat(machine);
}
pc_init1(machine, TYPE_I440FX_PCI_HOST_BRIDGE, \
TYPE_I440FX_PCI_DEVICE);
}
对应
- pc_machine_init_v8_1
static void pc_machine_init_v8_1(void)
{
type_register(&pc_machine_type_v8_1);
}
type_init(pc_machine_init_v8_1)
唯独这个pc_machine_init_v8_1图中没有明显对应对象,但根据type_init这个关键字,应对应图中这一部分:
再来回顾一下type_init,定义一个QEMU模块会调用type_init。type_init是一个宏,其定义在include/qemu/module.h中,如下:
#define type_init(function) module_init(function, MODULE_INIT_QOM)
因此,这里的
type_init(pc_machine_init_v8_1)
实际上是
static void __attribute__((constructor)) do_qemu_init_ ## function(void) \
{ \
register_module_init(function, type); \
}
module_init(pc_machine_init_v8_1, MODULE_INIT_QOM)
从代码中的定义就可以看出,type_init后边的参数是一个函数(function),调用type_init就相当于调用了module_init,而在这里的函数就是pc_machine_init_v8_1,类型就是MODULE_INIT_QOM。
module_init也是一个宏,其定义也在include/qemu/module.h中,如下:
/* This should not be used directly. Use block_init etc. instead. */
#define module_init(function, type) \
static void __attribute__((constructor)) do_qemu_init_ ## function(void) \
{ \
register_module_init(function, type); \
}
代入此处的实际值,为
static void __attribute__((constructor)) do_qemu_init_pc_machine_init_v8_1(void)
{
register_module_init(pc_machine_init_v8_1, MODULE_INIT_QOM);
}
由以上代码可知,module_init最终调用了register_module_init函数。register_module_init函数在util/module.c中,代码如下:
static void init_lists(void)
{
static int inited;
int i;
if (inited) {
return;
}
for (i = 0; i < MODULE_INIT_MAX; i++) {
QTAILQ_INIT(&init_type_list[i]);
}
QTAILQ_INIT(&dso_init_list);
inited = 1;
}
static ModuleTypeList *find_type(module_init_type type)
{
init_lists();
return &init_type_list[type];
}
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);
}
module_init_type的定义在include/qemu/module.h中,如下:
typedef enum {
MODULE_INIT_MIGRATION,
MODULE_INIT_BLOCK,
MODULE_INIT_OPTS,
MODULE_INIT_QOM,
MODULE_INIT_TRACE,
MODULE_INIT_XEN_BACKEND,
MODULE_INIT_LIBQOS,
MODULE_INIT_FUZZ_TARGET,
MODULE_INIT_MAX
} module_init_type;
属于MODULE_INIT_QOM这种类型的,有一个Module列表ModuleTypeList(就是上边代码中的init_type_list,其类型为ModuleTypeList),列表中是一项一项的ModuleEntry。register_module_init函数中会设置(初始化)每一项的init函数为函数参数中的fn(第1个参数)。此处的module的init函数就是pc_machine_init_v8_1。
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);
}
当然,MODULE_INIT_QOM这种类型会有很多很多的module,所有调用type_init的地方都会注册一个MODULE_INIT_QOM类型的Module。
type_init也可以说register_module_init函数只是完成了注册,也即设置了该module的init函数指针所指向的回调函数,真正调用此回调函数的地方是在module_call_init函数中,该函数也在util/module.c中,代码如下:
void module_call_init(module_init_type type)
{
ModuleTypeList *l;
ModuleEntry *e;
if (modules_init_done[type]) {
return;
}
l = find_type(type);
QTAILQ_FOREACH(e, l, node) {
e->init();
}
modules_init_done[type] = true;
}
在module_call_init函数中,会找到MODULE_INIT_QOM这种类型所对应的ModuleTypeList。
static ModuleTypeList *find_type(module_init_type type)
{
init_lists();
return &init_type_list[type];
}
而后找出(该)列表中所有的ModuleEntry,然后调用每个ModuleEntry的init函数。对应的就是module_call_init函数中的这一代码片段:
QTAILQ_FOREACH(e, l, node) {
e->init();
}
这里需要注意的是,在module_call_init函数调用的这一步,所有Module的init函数都已经被调用过了。也就是说,后文书会看到很多的Module,当看到它们的时候需要意识到,其init函数在此处已被调用过了。
对于这里的
type_init(pc_machine_init_v8_1)
module_call_init函数中的
e->init();
实际上调用的就是register_module_init函数中设置的
e->init = fn;
也就是pc_machine_init_v8_1函数。
static void pc_machine_init_v8_1(void)
{
type_register(&pc_machine_type_v8_1);
}
type_init(pc_machine_init_v8_1)
对于type_register函数的详细解析及这条主线的梳理,放在“下半场”即下一回中。