QEMU体系架构分析(2)

说在前面的话

         在之前的文章<>中,介绍了一些组成QEMU基本框架的一些元素或软件机制。但是从这些基本元素上你看不出QEMU是怎么工作的,看不出QEMU怎么模拟了一个虚拟机。这是因为这些元素只是QEMU的基础,QEMU在此基础上来构建并模拟虚拟机。

机器类型

         在QEMU的代码中,占有最大比重的就是模拟具体设备的代码。在QEMU中一个个设备就是一个个QOM对象。QEMU要模拟的虚拟机是由一个个设备组成的,这些设备有些是由用户在命令行参数指定的,有些是QEMU自动创建的。这是因为对于一台虚拟机上有哪些必要的设备,用户并不一定非常清楚,用户只需要指定他想要这台虚拟机有哪些设备,QEMU负责构建一个虚拟机并且创建一台虚拟机必须的一些基础设备,并且尊重用户的选择,如果用户指定了某个设备,则QEMU必须创建它。
         QEMU以什么原则来创建虚拟机呢?计算机有各种不同的模型,可以模拟真实的物理机,可以是PC机,也可以是服务器。很显然,创建哪种虚拟机也是交由用户选择的,当然用户如果没有指定机器类型就创建默认的虚拟机。由此可见同一架构下的虚拟机也是有不同的类型的,QEMU定义了一种基础QOM类型-机器类型(MachineClass),用来代表不同类型的虚拟机。用户使用-machine参数来指定创建的虚拟机类型。
         在创建和模拟虚拟机的过程中机器类型是一个核心的关键要素,它定义了要模拟的虚拟机的基本模型。机器类型与具体的设备类型的关系是,机器类型并不是虚拟机上所有设备的集合或者父类型,而是它定义了虚拟机上设备的整合方式,即这些设备如何组合成一个虚拟机。
         基本机器类型为MachineClass结构,其对应的对象结构体为MachineState类型,它们是所有机器类型的祖先类型和祖先对象。这两个结构体定义了所有的机器类型作为一个虚拟机所共有的一些接口和属性。比如定义了虚拟机的初始化接口函数、定义了支持的最大和最小的CPU数目,虚拟机上支不支持USB设备、虚拟机上的CPU结构体链表、虚拟机上运行的操作系统和dtb文件、内核bin文件、initrd文件、使用的加速器类型等。

QEMU创建一个虚拟机的过程

         我们以ARM64虚拟机的创建过程为例来介绍一个QEMU虚拟机的创建过程。从前文介绍我们知道,虚拟机的创建过程是以机器类型为中心来逐步建立起来的。过程如下:

1. 选择机器类型

         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机器类型在不断的改进,但是以前版本的机器类型必须同时存在,因为要支持以前已经创建虚拟机的迁移操作。

2. 初始化机器类型和对象

        在选择机器类型的时候,会顺便初始化机器类型。接着在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对象初始化时也是先初始化其父对象。

你可能感兴趣的:(虚拟化技术)