接前一篇文章:QEMU源码全解析41 —— Machine(11)
本文内容参考:
《趣谈Linux操作系统》 —— 刘超,极客时间
《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(上):如何复用集团的人力资源?):
欲知后事如何,且看下回分解。