接前一篇文章:QEMU源码全解析41 —— Machine(11)
本文内容参考:
《QEMU/KVM》源码解析与应用 —— 李强,机械工业出版社
特此致谢!
上一回针对于“Machine”系列第3~10篇文章开始梳理其脉络,梳理了type_init()这一条线,本文讲梳理type_register()这一条线,并对整个流程进行总结。
先再次贴出
DEFINE_I440FX_MACHINE(v8_1, "pc-i440fx-8.1", NULL,
pc_i440fx_8_1_machine_options);
的相关代码:
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)
type_register函数在qom/object.c中,代码如下:
TypeImpl *type_register(const TypeInfo *info)
{
assert(info->parent);
return type_register_internal(info);
}
type_register_internal函数就在上边,代码如下:
static TypeImpl *type_register_internal(const TypeInfo *info)
{
TypeImpl *ti;
ti = type_new(info);
type_table_add(ti);
return ti;
}
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_table_add函数也在qom/object.c中,代码如下:
static void type_table_add(TypeImpl *ti)
{
assert(!enumerating_types);
g_hash_table_insert(type_table_get(), (void *)ti->name, ti);
}
每一个Module既然要模拟某种设备,那么就应该定义一种类型TypeImpl来表示这个设备。这其实是一种面向对象编程的思路,只不过这里用的是纯C语言的实现,因此需要变相实现一下类和对象。
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函数会注册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-i440fx-8.1" TYPE_MACHINE_SUFFIX”,即"pc-i440fx-8.1-machine";这个类有父类TYPE_PC_MACHINE(即"generic-pc-machine");这个类的初始化应该调用函数pc_machine_v8_1_class_init。
这里的调用链为:
pc_machine_init_v8_1
type_register
type_register_internal
static void pc_machine_init_v8_1(void)
{
type_register(&pc_machine_type_v8_1);
}
static TypeImpl *type_register_internal(const TypeInfo *info)
{
TypeImpl *ti;
ti = type_new(info);
type_table_add(ti);
return ti;
}
在type_register_internal函数中,会根据pc_machine_type_v8_1这个TypeInfo ,创建一个TypeImpl来表示这个新注册的类,也就是说,TypeImpl才是真正想要声明的那个class。分别来看一下TypeInfo结构和TypeImpl结构的定义:
struct TypeInfo的定义在include/qom/object.h中,如下:
struct TypeInfo
{
const char *name;
const char *parent;
size_t instance_size;
size_t instance_align;
void (*instance_init)(Object *obj);
void (*instance_post_init)(Object *obj);
void (*instance_finalize)(Object *obj);
bool abstract;
size_t class_size;
void (*class_init)(ObjectClass *klass, void *data);
void (*class_base_init)(ObjectClass *klass, void *data);
void *class_data;
InterfaceInfo *interfaces;
};
struct TypeImpl的定义在qom/object.c中,如下:
struct TypeImpl
{
const char *name;
size_t class_size;
size_t instance_size;
size_t instance_align;
void (*class_init)(ObjectClass *klass, void *data);
void (*class_base_init)(ObjectClass *klass, void *data);
void *class_data;
void (*instance_init)(Object *obj);
void (*instance_post_init)(Object *obj);
void (*instance_finalize)(Object *obj);
bool abstract;
const char *parent;
TypeImpl *parent_type;
ObjectClass *class;
int num_interfaces;
InterfaceImpl interfaces[MAX_INTERFACES];
};
由定义可见,TypeInfo结构和TypeImpl结构还是很相近的。
在QEMU中,有一个全局的哈希表type_table,用来存放所有定义的类。在type_new函数中,先从type_table表中根据名字查找某个类(此处是pc_machine_type_v8_1)。如果找到,说明这个类已经被注册过了,报错并终止当前进程;如果没有找到,则说明这是一个新的类,那么就将TypeInfo里边的信息填到TypeImpl中。
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_register_internal函数会调用type_table_add函数,将这个类注册到全局的表中。
static TypeImpl *type_register_internal(const TypeInfo *info)
{
TypeImpl *ti;
ti = type_new(info);
type_table_add(ti);
return ti;
}
到这里,class_init还没有被调用,也即这个类还处于纸面的状态。
此处与Java中的反射机制有些类似。在Java中,对于一个类,首先写代码的时候要写一个class xxx的定义,编译好后就放在.class文件中,这也是处于纸面的状态。然后,Java中会有一个Class对象,用于读取和表示这个纸面上的class xxx,从而生成真正的对象。
在QEMU中,也会有相类似的过程。class_init会生成XXXClass,相当于Java中的Class xxx;TypeImpl中还会有一个instance_init函数,相当于构造函数,用于根据XXXClass生成Object,这就相当于Java反射中最终创建的对象。和构造函数对应的还有instance_finalize,相当于析构函数。
分析完了type_register这一支,正式开始解析主流程。
在QEMU的老版本中,主函数main中直接调用select_machine函数,而在新版本中,则是如下调用流程:
main()
qemu_main()
qemu_init()
qemu_create_machine
select_machine()
qemu_create_machine函数在softmmu/vl.c中,代码如下:
static void qemu_create_machine(QDict *qdict)
{
MachineClass *machine_class = select_machine(qdict, &error_fatal);
object_set_machine_compat_props(machine_class->compat_props);
current_machine = MACHINE(object_new_with_class(OBJECT_CLASS(machine_class)));
object_property_add_child(object_get_root(), "machine",
OBJECT(current_machine));
object_property_add_child(container_get(OBJECT(current_machine),
"/unattached"),
"sysbus", OBJECT(sysbus_get_default()));
if (machine_class->minimum_page_bits) {
if (!set_preferred_target_page_bits(machine_class->minimum_page_bits)) {
/* This would be a board error: specifying a minimum smaller than
* a target's compile-time fixed setting.
*/
g_assert_not_reached();
}
}
cpu_exec_init_all();
page_size_init();
if (machine_class->hw_version) {
qemu_set_hw_version(machine_class->hw_version);
}
/*
* Get the default machine options from the machine if it is not already
* specified either by the configuration file or by the command line.
*/
if (machine_class->default_machine_opts) {
QDict *default_opts =
keyval_parse(machine_class->default_machine_opts, NULL, NULL,
&error_abort);
qemu_apply_legacy_machine_options(default_opts);
object_set_properties_from_keyval(OBJECT(current_machine), default_opts,
false, &error_abort);
qobject_unref(default_opts);
}
}
其中第1行代码就是select_machine函数,代码片段如下:
MachineClass *machine_class = select_machine(qdict, &error_fatal);
顾名思义,select_machine函数的作用是选择一个MachineClass,其可能由用户指定,如果用户未指定,则采用系统默认。如果是后者,QEMU最新版本号对应的机器类型为默认设置。由于笔者的源码为qemu-8.1.4,因此默认机器类型是pc-i440fx-8.1-machine。而这就对应于:
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);
}
select_machine函数同样在softmmu/vl.c中,代码如下:
static MachineClass *select_machine(QDict *qdict, Error **errp)
{
const char *optarg = qdict_get_try_str(qdict, "type");
GSList *machines = object_class_get_list(TYPE_MACHINE, false);
MachineClass *machine_class;
Error *local_err = NULL;
if (optarg) {
machine_class = find_machine(optarg, machines);
qdict_del(qdict, "type");
if (!machine_class) {
error_setg(&local_err, "unsupported machine type");
}
} else {
machine_class = find_default_machine(machines);
if (!machine_class) {
error_setg(&local_err, "No machine specified, and there is no default");
}
}
g_slist_free(machines);
if (local_err) {
error_append_hint(&local_err, "Use -machine help to list supported machines\n");
error_propagate(errp, local_err);
}
return machine_class;
}
如上面所讲,在select_machine函数中,有两种方式可以生成MachineClass:一种方式是调用find_machine函数,通过解析QEMU命令行参数生成MachineClass,即用户指定方式;另一种方式是通过find_default_machine函数找一个默认的MachineClass,即系统默认方式。
无论是用户指定还是系统默认方式,都得先调用object_class_get_list函数获得一个MachineClass列表,然后在里边找。代码片段如下:
GSList *machines = object_class_get_list(TYPE_MACHINE, false);
object_class_get_list函数在qom/object.c中,代码如下:
GSList *object_class_get_list(const char *implements_type,
bool include_abstract)
{
GSList *list = NULL;
object_class_foreach(object_class_get_list_tramp,
implements_type, include_abstract, &list);
return list;
}
object_class_foreach函数在同文件中,代码如下:
void object_class_foreach(void (*fn)(ObjectClass *klass, void *opaque),
const char *implements_type, bool include_abstract,
void *opaque)
{
OCFData data = { fn, implements_type, include_abstract, opaque };
enumerating_types = true;
g_hash_table_foreach(type_table_get(), object_class_foreach_tramp, &data);
enumerating_types = false;
}
在全局表type_table_get()中,对于每一项TypeImpl,都执行object_class_foreach_tramp。object_class_foreach_tramp函数在qom/object.c中。代码如下:
static void object_class_foreach_tramp(gpointer key, gpointer value,
gpointer opaque)
{
OCFData *data = opaque;
TypeImpl *type = value;
ObjectClass *k;
type_initialize(type);
k = type->class;
if (!data->include_abstract && type->abstract) {
return;
}
if (data->implements_type &&
!object_class_dynamic_cast(k, data->implements_type)) {
return;
}
data->fn(k, data->opaque);
}
在object_class_foreach_tramp函数中,会调用type_initialize函数,该函数在同文件中,代码如下:
static void type_initialize(TypeImpl *ti)
{
TypeImpl *parent;
if (ti->class) {
return;
}
ti->class_size = type_class_get_size(ti);
ti->instance_size = type_object_get_size(ti);
/* Any type with zero instance_size is implicitly abstract.
* This means interface types are all abstract.
*/
if (ti->instance_size == 0) {
ti->abstract = true;
}
if (type_is_ancestor(ti, type_interface)) {
assert(ti->instance_size == 0);
assert(ti->abstract);
assert(!ti->instance_init);
assert(!ti->instance_post_init);
assert(!ti->instance_finalize);
assert(!ti->num_interfaces);
}
ti->class = g_malloc0(ti->class_size);
parent = type_get_parent(ti);
if (parent) {
type_initialize(parent);
GSList *e;
int i;
g_assert(parent->class_size <= ti->class_size);
g_assert(parent->instance_size <= ti->instance_size);
memcpy(ti->class, parent->class, parent->class_size);
ti->class->interfaces = NULL;
for (e = parent->class->interfaces; e; e = e->next) {
InterfaceClass *iface = e->data;
ObjectClass *klass = OBJECT_CLASS(iface);
type_initialize_interface(ti, iface->interface_type, klass->type);
}
for (i = 0; i < ti->num_interfaces; i++) {
TypeImpl *t = type_get_by_name(ti->interfaces[i].typename);
if (!t) {
error_report("missing interface '%s' for object '%s'",
ti->interfaces[i].typename, parent->name);
abort();
}
for (e = ti->class->interfaces; e; e = e->next) {
TypeImpl *target_type = OBJECT_CLASS(e->data)->type;
if (type_is_ancestor(target_type, t)) {
break;
}
}
if (e) {
continue;
}
type_initialize_interface(ti, t, t);
}
}
ti->class->properties = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
object_property_free);
ti->class->type = ti;
while (parent) {
if (parent->class_base_init) {
parent->class_base_init(ti->class, ti->class_data);
}
parent = type_get_parent(parent);
}
if (ti->class_init) {
ti->class_init(ti->class, ti->class_data);
}
}
在type_initialize函数中(最后一部分),会调用class_init,将纸面上的class也即TypeImpl变为ObjectClass。ObjectClass是所有类的祖先,MachineClass是它的子类。在这里,肯定可以找到之前注册过的TypeImpl,并调用其class_init函数。
因而,pc_machine_##suffix##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_i440fx_machine_options才真正由pc_i440fx_8_1machine_options调用,从而初始化MachineClass。
pc_i440fx_8_1machine_options函数在hw/i386/pc_piix.c中,代码如下:
static void pc_i440fx_8_1_machine_options(MachineClass *m)
{
pc_i440fx_machine_options(m);
m->alias = "pc";
m->is_default = true;
}
pc_i440fx_machine_options函数就在上边,代码如下:
static void pc_i440fx_machine_options(MachineClass *m)
{
PCMachineClass *pcmc = PC_MACHINE_CLASS(m);
pcmc->pci_root_uid = 0;
pcmc->default_cpu_version = 1;
m->family = "pc_piix";
m->desc = "Standard PC (i440FX + PIIX, 1996)";
m->default_machine_opts = "firmware=bios-256k.bin";
m->default_display = "std";
m->default_nic = "e1000";
m->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE);
}
pc_machine_v8_1_class_init函数接下来将MachineClass的init函数设置为pc_init##suffix。
mc->init = pc_init_v8_1;
因此,在select_machine函数执行完毕后,就有一个MachineClass了。
MachineClass的定义在include/qemu/typedefs.h中,如下:
typedef struct MachineClass MachineClass;
struct MachineClass的定义在include/hw/boards.h中,如下:
struct MachineClass {
/*< private >*/
ObjectClass parent_class;
/*< public >*/
const char *family; /* NULL iff @name identifies a standalone machtype */
char *name;
const char *alias;
const char *desc;
const char *deprecation_reason;
void (*init)(MachineState *state);
void (*reset)(MachineState *state, ShutdownCause reason);
void (*wakeup)(MachineState *state);
int (*kvm_type)(MachineState *machine, const char *arg);
BlockInterfaceType block_default_type;
int units_per_default_bus;
int max_cpus;
int min_cpus;
int default_cpus;
unsigned int no_serial:1,
no_parallel:1,
no_floppy:1,
no_cdrom:1,
no_sdcard:1,
pci_allow_0_address:1,
legacy_fw_cfg_order:1;
bool is_default;
const char *default_machine_opts;
const char *default_boot_order;
const char *default_display;
const char *default_nic;
GPtrArray *compat_props;
const char *hw_version;
ram_addr_t default_ram_size;
const char *default_cpu_type;
bool default_kernel_irqchip_split;
bool option_rom_has_mr;
bool rom_file_has_mr;
int minimum_page_bits;
bool has_hotpluggable_cpus;
bool ignore_memory_transaction_failures;
int numa_mem_align_shift;
const char **valid_cpu_types;
strList *allowed_dynamic_sysbus_devices;
bool auto_enable_numa_with_memhp;
bool auto_enable_numa_with_memdev;
bool ignore_boot_device_suffixes;
bool smbus_no_migration_support;
bool nvdimm_supported;
bool numa_mem_supported;
bool auto_enable_numa;
bool cpu_cluster_has_numa_boundary;
SMPCompatProps smp_props;
const char *default_ram_id;
HotplugHandler *(*get_hotplug_handler)(MachineState *machine,
DeviceState *dev);
bool (*hotplug_allowed)(MachineState *state, DeviceState *dev,
Error **errp);
CpuInstanceProperties (*cpu_index_to_instance_props)(MachineState *machine,
unsigned cpu_index);
const CPUArchIdList *(*possible_cpu_arch_ids)(MachineState *machine);
int64_t (*get_default_cpu_node_id)(const MachineState *ms, int idx);
ram_addr_t (*fixup_ram_size)(ram_addr_t size);
};
回到qemu_create_machine函数中,
static void qemu_create_machine(QDict *qdict)
{
MachineClass *machine_class = select_machine(qdict, &error_fatal);
object_set_machine_compat_props(machine_class->compat_props);
current_machine = MACHINE(object_new_with_class(OBJECT_CLASS(machine_class)));
object_property_add_child(object_get_root(), "machine",
OBJECT(current_machine));
object_property_add_child(container_get(OBJECT(current_machine),
"/unattached"),
"sysbus", OBJECT(sysbus_get_default()));
if (machine_class->minimum_page_bits) {
if (!set_preferred_target_page_bits(machine_class->minimum_page_bits)) {
/* This would be a board error: specifying a minimum smaller than
* a target's compile-time fixed setting.
*/
g_assert_not_reached();
}
}
cpu_exec_init_all();
page_size_init();
if (machine_class->hw_version) {
qemu_set_hw_version(machine_class->hw_version);
}
/*
* Get the default machine options from the machine if it is not already
* specified either by the configuration file or by the command line.
*/
if (machine_class->default_machine_opts) {
QDict *default_opts =
keyval_parse(machine_class->default_machine_opts, NULL, NULL,
&error_abort);
qemu_apply_legacy_machine_options(default_opts);
object_set_properties_from_keyval(OBJECT(current_machine), default_opts,
false, &error_abort);
qobject_unref(default_opts);
}
}
在select_machine函数执行完毕后,即获得了一个MachineClass之后,接下来来到以下代码片段:
current_machine = MACHINE(object_new_with_class(OBJECT_CLASS(machine_class)));
MACHINE函数的代码在include/hw/boards.h中,如下:
static inline G_GNUC_UNUSED MachineState *MACHINE(const void *obj)
{
return OBJECT_CHECK(MachineState, obj, TYPE_MACHINE);
}
OBJECT_CHECK宏展开后的函数代码如下:
static inline G_GNUC_UNUSED MachineState *MACHINE(const void *obj)
{
return ((MachineState*)object_dynamic_cast_assert(OBJECT(obj), ("machine"), __FILE__, __LINE__, __func__));
}
现在要回过头来看qemu_create_machine函数(softmmu/vl.c中)调用MACHINE函数时,传递给它的实参:object_new(object_class_get_name(OBJECT_CLASS(machine_class)))。
现在object_new_with_class以及object_new函数成为了关注焦点。这两个函数都在qom/object.c中,代码分别如下:
Object *object_new(const char *typename)
{
TypeImpl *ti = type_get_by_name(typename);
return object_new_with_type(ti);
}
Object *object_new_with_class(ObjectClass *klass)
{
return object_new_with_type(klass->type);
}
可以看到,甭管是哪一个函数,最终都会调用到object_new_with_type函数。
object_new_with_type函数也在qom/object.c中,代码如下:
static Object *object_new_with_type(Type type)
{
Object *obj;
size_t size, align;
void (*obj_free)(void *);
g_assert(type != NULL);
type_initialize(type);
size = type->instance_size;
align = type->instance_align;
/*
* Do not use qemu_memalign unless required. Depending on the
* implementation, extra alignment implies extra overhead.
*/
if (likely(align <= __alignof__(qemu_max_align_t))) {
obj = g_malloc(size);
obj_free = g_free;
} else {
obj = qemu_memalign(align, size);
obj_free = qemu_vfree;
}
object_initialize_with_type(obj, size, type);
obj->free = obj_free;
return obj;
}
回到object_new函数中。TypeImpl的instance_init会被调用,创建一个对象。current_machine就是这个对象,其类型是MachineState。
Object *object_new(const char *typename)
{
TypeImpl *ti = type_get_by_name(typename);
return object_new_with_type(ti);
}
至此,兜兜转转一大圈,相关体系结构的对象才创建完毕。整体流程如下图所示(图片援引《趣谈Linux系统》50 | 计算虚拟化之CPU(上):如何复用集团的人力资源?):
欲知后事如何,且看下回分解。