接前一篇文章: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函数的详细解析及这条主线的梳理,放在“下半场”即下一回中。