接前一篇文章: 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(上):如何复用集团的人力资源? ):
欲知后事如何,且看下回分解。