在之前的文章<
在QEMU的代码中,占有最大比重的就是模拟具体设备的代码。在QEMU中一个个设备就是一个个QOM对象。QEMU要模拟的虚拟机是由一个个设备组成的,这些设备有些是由用户在命令行参数指定的,有些是QEMU自动创建的。这是因为对于一台虚拟机上有哪些必要的设备,用户并不一定非常清楚,用户只需要指定他想要这台虚拟机有哪些设备,QEMU负责构建一个虚拟机并且创建一台虚拟机必须的一些基础设备,并且尊重用户的选择,如果用户指定了某个设备,则QEMU必须创建它。
QEMU以什么原则来创建虚拟机呢?计算机有各种不同的模型,可以模拟真实的物理机,可以是PC机,也可以是服务器。很显然,创建哪种虚拟机也是交由用户选择的,当然用户如果没有指定机器类型就创建默认的虚拟机。由此可见同一架构下的虚拟机也是有不同的类型的,QEMU定义了一种基础QOM类型-机器类型(MachineClass),用来代表不同类型的虚拟机。用户使用-machine参数来指定创建的虚拟机类型。
在创建和模拟虚拟机的过程中机器类型是一个核心的关键要素,它定义了要模拟的虚拟机的基本模型。机器类型与具体的设备类型的关系是,机器类型并不是虚拟机上所有设备的集合或者父类型,而是它定义了虚拟机上设备的整合方式,即这些设备如何组合成一个虚拟机。
基本机器类型为MachineClass结构,其对应的对象结构体为MachineState类型,它们是所有机器类型的祖先类型和祖先对象。这两个结构体定义了所有的机器类型作为一个虚拟机所共有的一些接口和属性。比如定义了虚拟机的初始化接口函数、定义了支持的最大和最小的CPU数目,虚拟机上支不支持USB设备、虚拟机上的CPU结构体链表、虚拟机上运行的操作系统和dtb文件、内核bin文件、initrd文件、使用的加速器类型等。
我们以ARM64虚拟机的创建过程为例来介绍一个QEMU虚拟机的创建过程。从前文介绍我们知道,虚拟机的创建过程是以机器类型为中心来逐步建立起来的。过程如下:
QEMU预先定义了很多种机器类型(当然不同的平台会不一样),用户可以通过-machine参数指定虚拟机使用哪中机器类型,如果没有指定,则会选择默认的机器类型。
在QEMU主函数中,首先就是解析QEMU的参数,把QEMU的选项存入选项数据库中。然后会调用select_machine函数,根据QEMU的-machine参数找出要模拟的机器类型,并返回该类型。
2624 static MachineClass *select_machine(void)
2625 {
2626 MachineClass *machine_class = find_default_machine();
2627 const char *optarg;
2628 QemuOpts *opts;
2629 Location loc;
2630
2631 loc_push_none(&loc);
2632
2633 opts = qemu_get_machine_opts();
2634 qemu_opts_loc_restore(opts);
2635
2636 optarg = qemu_opt_get(opts, "type");
2637 if (optarg) {
2638 machine_class = machine_parse(optarg);
2639 }
2640
2641 if (!machine_class) {
2642 error_report("No machine specified, and there is no default");
2643 error_printf("Use -machine help to list supported machines\n");
2644 exit(1);
2645 }
2646
2647 loc_pop(&loc);
2648 2648 return machine_class;
2649 }
2626行,find_default_machine函数从类型的数据库中找到默认的机器类型。
2633行,qemu_get_machine_opts函数从选项数据库中找出"machine"大选项的实例(显然,这个大选项只会有一个实例)。
2636行,qemu_opt_get函数从machine选项实例中找到type子选项的value,这个type就是机器类型的名称,arm64下一般指定的是"virt"(通过-machine virt指定)。
2638行, machine_parse函数会根据机器类型的名字从类型数据库中找到对应的机器类型,这个过程中如果给机器类型还没有初始化,会调用type_initialize函数初始化该类型。
ARM64的"virt"机器类型是在QEMU的hw/arm/virt.c文件中实现的。这个文件中定义了很多个不同版本的virt机器类型"virt-major-minor",其中"major-minor"是其版本号。virt机器类型之同时所以存在这么多版本是为了保持与之前的版本兼容。随着时间推移,virt机器类型在不断的改进,但是以前版本的机器类型必须同时存在,因为要支持以前已经创建虚拟机的迁移操作。
在选择机器类型的时候,会顺便初始化机器类型。接着在qemu的main函数中会会初始化机器类型的对象。
3896 current_machine = MACHINE(object_new(object_class_get_name(
3897 OBJECT_CLASS(machine_class))));
3898 if (machine_help_func(qemu_get_machine_opts(), current_machine)) {
3899 exit(0);
3900 }
3901 object_property_add_child(object_get_root(), "machine",
3902 OBJECT(current_machine), &error_abort);
3903 object_property_add_child(container_get(OBJECT(current_machine),
3904 "/unattached"),
3905 "sysbus", OBJECT(sysbus_get_default()),
3906 NULL);
3896行,调用object_new初始化之前选择的机器类型的对象。
3901行,object_property_add_child是为对象添加一个指向其他对象的属性,这里分别为跟容器节点添加machine属性,指向机器类型对象。
3902行,为machine对象的容器对象添加sysbus属性,指向默认的sysbus对象。
这里面涉及到了容器(container)对象,容器类型和对象也是直接继承TYPE_OBJECT类型的一种基础类型,从名称就可以看出,容器对象是一种用了容纳其他对象的工具对象,它帮助把要模拟的虚拟机的所有对象连接成一个树型结构,其中树结构的每一个节点都有一个路径,父容器通过子路径名属性指向下一个节点的容器。3901行,创建根容器对象用来容纳机器类型对象。3902行,创建"/unattached"路径的容器节点来容纳sysbus对象。container_get函数查找病创建指定路径的容器对象。
我们以ARM64平台的"virt"机器类型为例来看看机器类型和对象是怎么创建的。“virt”机器类型的定义如下:
2029 static const TypeInfo virt_machine_info = {
2030 .name = TYPE_VIRT_MACHINE,
2031 .parent = TYPE_MACHINE,
2032 .abstract = true,
2033 .instance_size = sizeof(VirtMachineState),
2034 .class_size = sizeof(VirtMachineClass),
2035 .class_init = virt_machine_class_init,
2036 .instance_init = virt_instance_init,
2037 .interfaces = (InterfaceInfo[]) {
2038 { TYPE_HOTPLUG_HANDLER },
2039 { }
2040 },
2041 };
101 typedef struct {
102 MachineClass parent;
103 bool disallow_affinity_adjustment;
104 bool no_its; //该机器类型是否支持its中断控制器
105 bool no_pmu; //该机器类型是否支持pmu设备
106 bool claim_edge_triggered_timers;
107 bool smbios_old_sys_ver;
108 bool no_highmem_ecam;
109 } VirtMachineClass;
110
111 typedef struct {
112 MachineState parent;
113 Notifier machine_done;
114 DeviceState *platform_bus_dev;
115 FWCfgState *fw_cfg;
116 PFlashCFI01 *flash[2];
117 bool secure;
118 bool highmem;
119 bool highmem_ecam;
120 bool its;
121 bool virt;
122 int32_t gic_version;
123 VirtIOMMUType iommu;
124 struct arm_boot_info bootinfo;
125 MemMapEntry *memmap;
126 const int *irqmap;
127 int smp_cpus;
128 void *fdt;
129 int fdt_size;
130 uint32_t clock_phandle;
131 uint32_t gic_phandle;
132 uint32_t msi_phandle;
133 uint32_t iommu_phandle;
134 int psci_conduit;
135 hwaddr highest_gpa;
136 } VirtMachineState;
在TYPE_VIRT_MACHINE的机器类型初始化和对象初始化时会分配VirtMachineClass机器类型类结构体和VirtMachineState机器类型对象结构体。
我们先看看类型的初始化。
在机器类型初始化的同时会先其父类型TYPE_MACHINE,TYPE_MACINE的类型初始化函数会基本的使用所有机器类型的初始化操作,比如设置默认的内存大小,为机器类型添加了很多基础属性(“accel”、“kernel-irqchip”、“kernel”、“initrd”)等。然后会调用 TYPE_VIRT_MACHINE的类型初始化函数 virt_machine_class_init。
1747 static void virt_machine_class_init(ObjectClass *oc, void *data)
1748 {
1749 MachineClass *mc = MACHINE_CLASS(oc);
1750 HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc);
1751
1752 mc->init = machvirt_init;
1753 /* Start with max_cpus set to 512, which is the maximum supported by KVM.
1754 * The value may be reduced later when we have more information about the
1755 * configuration of the particular instance.
1756 */
1757 mc->max_cpus = 512;
1758 machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_CALXEDA_XGMAC);
1759 machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_AMD_XGBE);
1760 machine_class_allow_dynamic_sysbus_dev(mc, TYPE_RAMFB_DEVICE);
1761 mc->block_default_type = IF_VIRTIO;
1762 mc->no_cdrom = 1;
1763 mc->pci_allow_0_address = true;
1764 /* We know we will never create a pre-ARMv7 CPU which needs 1K pages */
1765 mc->minimum_page_bits = 12;
1766 mc->possible_cpu_arch_ids = virt_possible_cpu_arch_ids;
1767 mc->cpu_index_to_instance_props = virt_cpu_index_to_props;
1768 mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a15");
1769 mc->get_default_cpu_node_id = virt_get_default_cpu_node_id;
1770 assert(!mc->get_hotplug_handler);
1771 mc->get_hotplug_handler = virt_machine_get_hotplug_handler;
1772 hc->plug = virt_machine_device_plug_cb;
1773 }
1747行,赋值mc->init回调函数为machvirt_init函数,这个函数非常重要,是真正要模拟的虚拟机的初始化函数,他们创建并初始化CPU、内存等所有的虚拟设备。
设置该虚拟机支持的最大的CPU个数为512个。
1758~1760,使能3个能够动态创建的sysbus设备。
1761行,设置块设备默认类型为virtio类型。
1765行,设置最小哦page_bits为12也就是支持的最小内存页为4K大小的页。
1766行,设置mc->possible_cpu_arch_ids回调函数为virt_possible_cpu_arch_ids函数,该回调函数是为虚拟机的所有CPU分配CPUArchId结构体的。CPUArchId结构体保存在机器类型对象MachineState结构体的possible_cpus成员中,CPUArchId结构体的cpu和type字段会指向CPU类型对象,从而把机器类型和CPU关联起来。
1767行,设置mc->cpu_index_to_instance_props回调函数为 virt_cpu_index_to_props。
virt_machine_class_init主要进行TYPE_VIRT_MACHINE相关的一些机器类型的类初始化。
我们再来看TYPE_VIRT_MACHINE对象的初始化。
TYPE_VIRT_MACHINE对象初始化时也是先初始化其父对象。