接前一篇文章: QEMU源码全解析22 —— QOM介绍(11)
本文内容参考:
《 QEMU /KVM》源码解析与应用 —— 李强,机械工业出版社
特此致谢!
上一回分析了 QEMU 对象的构造和初始化的函数调用流程,本回结合实例对此流程进行深入介绍和解析。
仍以前文的edu的TypeInfo为例。再次贴出edu相关代码如下(hw/misc/edu.c中):
- static void pci_edu_register_types(void)
- {
- static InterfaceInfo interfaces[] = {
- { INTERFACE_CONVENTIONAL_PCI_DEVICE },
- { },
- };
- static const TypeInfo edu_info = {
- .name = TYPE_PCI_EDU_DEVICE,
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(EduState),
- .instance_init = edu_instance_init,
- .class_init = edu_class_init,
- .interfaces = interfaces,
- };
-
- type_register_static(&edu_info);
- }
- type_init(pci_edu_register_types)
edu的对象大小为sizeof(EduState),因此实际上一个edu类型的对象是EduState结构体,每一个对象都会有一个XXXState与之对应,记录了该对象的相关信息。若edu是一个PCI设备,则EduState中就会有这个设备的一些信息,如:中断信息、设备状态、使用的MMIO和PIO对应的内存区域等。
在object_init_with_type函数中可以看到,调用的参数都是一个Object,却能够一直调用父类型的初始化函数,不出意外这里也会有一个层次关系。为了便于理解,再次贴出相关代码:
- static void object_init_with_type(Object *obj, TypeImpl *ti)
- {
- if (type_has_parent(ti)) {
- object_init_with_type(obj, type_get_parent(ti));
- }
-
- if (ti->instance_init) {
- ti->instance_init(obj);
- }
- }
具体来看一下EduState结构,其定义在hw/misc/edu.c中,如下所示:
- struct EduState {
- PCIDevice pdev;
- MemoryRegion mmio;
-
- QemuThread thread;
- QemuMutex thr_mutex;
- QemuCond thr_cond;
- bool stopping;
-
- uint32_t addr4;
- uint32_t fact;
- #define EDU_STATUS_COMPUTING 0x01
- #define EDU_STATUS_IRQFACT 0x80
- uint32_t status;
-
- uint32_t irq_status;
-
- #define EDU_DMA_RUN 0x1
- #define EDU_DMA_DIR(cmd) (((cmd) & 0x2) >> 1)
- # define EDU_DMA_FROM_PCI 0
- # define EDU_DMA_TO_PCI 1
- #define EDU_DMA_IRQ 0x4
- struct dma_state {
- dma_addr_t src;
- dma_addr_t dst;
- dma_addr_t cnt;
- dma_addr_t cmd;
- } dma;
- QEMUTimer dma_timer;
- char dma_buf[DMA_SIZE];
- uint64_t dma_mask;
- };
关注其中第一个成员的类型:PCIDevice结构。其与pci_device_type_info对应,代码如下(hw/pci/pci.c中):
- static const TypeInfo pci_device_type_info = {
- .name = TYPE_PCI_DEVICE,
- .parent = TYPE_DEVICE,
- .instance_size = sizeof(PCIDevice),
- .abstract = true,
- .class_size = sizeof(PCIDeviceClass),
- .class_init = pci_device_class_init,
- .class_base_init = pci_device_class_base_init,
- };
struct PCIDevice在include/hw/pci/pci.h中定义,代码如下:
- struct PCIDevice {
- DeviceState qdev;
- bool partially_hotplugged;
- bool has_power;
-
- /* PCI config space */
- uint8_t *config;
-
- /* Used to enable config checks on load. Note that writable bits are
- * never checked even if set in cmask. */
- uint8_t *cmask;
-
- /* Used to implement R/W bytes */
- uint8_t *wmask;
-
- /* Used to implement RW1C(Write 1 to Clear) bytes */
- uint8_t *w1cmask;
-
- /* Used to allocate config space for capabilities. */
- uint8_t *used;
-
- /* the following fields are read only */
- int32_t devfn;
- /* Cached device to fetch requester ID from, to avoid the PCI
- * tree walking every time we invoke PCI request (e.g.,
- * MSI). For conventional PCI root complex, this field is
- * meaningless. */
- PCIReqIDCache requester_id_cache;
- char name[64];
- PCIIORegion io_regions[PCI_NUM_REGIONS];
- AddressSpace bus_master_as;
- MemoryRegion bus_master_container_region;
- MemoryRegion bus_master_enable_region;
-
- /* do not access the following fields */
- PCIConfigReadFunc *config_read;
- PCIConfigWriteFunc *config_write;
-
- /* Legacy PCI VGA regions */
- MemoryRegion *vga_regions[QEMU_PCI_VGA_NUM_REGIONS];
- bool has_vga;
-
- /* Current IRQ levels. Used internally by the generic PCI code. */
- uint8_t irq_state;
-
- /* Capability bits */
- uint32_t cap_present;
-
- /* Offset of MSI-X capability in config space */
- uint8_t msix_cap;
-
- /* MSI-X entries */
- int msix_entries_nr;
-
- /* Space to store MSIX table & pending bit array */
- uint8_t *msix_table;
- uint8_t *msix_pba;
-
- /* May be used by INTx or MSI during interrupt notification */
- void *irq_opaque;
-
- MSITriggerFunc *msi_trigger;
- MSIPrepareMessageFunc *msi_prepare_message;
- MSIxPrepareMessageFunc *msix_prepare_message;
-
- /* MemoryRegion container for msix exclusive BAR setup */
- MemoryRegion msix_exclusive_bar;
- /* Memory Regions for MSIX table and pending bit entries. */
- MemoryRegion msix_table_mmio;
- MemoryRegion msix_pba_mmio;
- /* Reference-count for entries actually in use by driver. */
- unsigned *msix_entry_used;
- /* MSIX function mask set or MSIX disabled */
- bool msix_function_masked;
- /* Version id needed for VMState */
- int32_t version_id;
-
- /* Offset of MSI capability in config space */
- uint8_t msi_cap;
-
- /* PCI Express */
- PCIExpressDevice exp;
-
- /* SHPC */
- SHPCDevice *shpc;
-
- /* Location of option rom */
- char *romfile;
- uint32_t romsize;
- bool has_rom;
- MemoryRegion rom;
- uint32_t rom_bar;
-
- /* INTx routing notifier */
- PCIINTxRoutingNotifier intx_routing_notifier;
-
- /* MSI-X notifiers */
- MSIVectorUseNotifier msix_vector_use_notifier;
- MSIVectorReleaseNotifier msix_vector_release_notifier;
- MSIVectorPollNotifier msix_vector_poll_notifier;
-
- /* ID of standby device in net_failover pair */
- char *failover_pair_id;
- uint32_t acpi_index;
- };
同样关注第一个成员的类型:DeviceState结构。其与device_type_info对应,代码如下(hw/core/qdev.c):
- static const TypeInfo device_type_info = {
- .name = TYPE_DEVICE,
- .parent = TYPE_OBJECT,
- .instance_size = sizeof(DeviceState),
- .instance_init = device_initfn,
- .instance_post_init = device_post_init,
- .instance_finalize = device_finalize,
- .class_base_init = device_class_base_init,
- .class_init = device_class_init,
- .abstract = true,
- .class_size = sizeof(DeviceClass),
- .interfaces = (InterfaceInfo[]) {
- { TYPE_VMSTATE_IF },
- { TYPE_RESETTABLE_INTERFACE },
- { }
- }
- };
struct DeviceState在include/hw/qdev-core.h中定义,代码如下:
- /**
- * DeviceState:
- * @realized: Indicates whether the device has been fully constructed.
- * When accessed outside big qemu lock, must be accessed with
- * qatomic_load_acquire()
- * @reset: ResettableState for the device; handled by Resettable interface.
- *
- * This structure should not be accessed directly. We declare it here
- * so that it can be embedded in individual device state structures.
- */
- struct DeviceState {
- /*< private >*/
- Object parent_obj;
- /*< public >*/
-
- char *id;
- char *canonical_path;
- bool realized;
- bool pending_deleted_event;
- int64_t pending_deleted_expires_ms;
- QDict *opts;
- int hotplugged;
- bool allow_unplug_during_migration;
- BusState *parent_bus;
- QLIST_HEAD(, NamedGPIOList) gpios;
- QLIST_HEAD(, NamedClockList) clocks;
- QLIST_HEAD(, BusState) child_bus;
- int num_child_bus;
- int instance_id_alias;
- int alias_required_for_version;
- ResettableState reset;
- GSList *unplug_blockers;
- };
通过以上edu_info、pci_device_type_info、device_type_info和与之对应的EduState、PCIDevice、DeviceState的定义可以看出,对象之间其实也是有一种父对象与子对象的关系存在。与类型一样,QOM中的对象也可以使用宏,将一个指向Object对象的指针转换成一个指向子类对象的指针,转换过程与类型ObjectClass类似,在此不在赘述。
这里可以看出,不同于类型信息和类型,object是根据需要创建的,只有在命令行指定了设备或者是热插拔一个设备之后才会有object的创建。类型和对象之间是通过Object的Class域联系在一起的。这是在object_initialize_with_type函数中通过obj->class = type->class实现的。对应代码再次贴出(qom/object.c中):
- static void object_initialize_with_type(Object *obj, size_t size, TypeImpl *type)
- {
- type_initialize(type);
-
- g_assert(type->instance_size >= sizeof(Object));
- g_assert(type->abstract == false);
- g_assert(size >= type->instance_size);
-
- memset(obj, 0, type->instance_size);
- obj->class = type->class;
- object_ref(obj);
- object_class_property_init_all(obj);
- obj->properties = g_hash_table_new_full(g_str_hash, g_str_equal,
- NULL, object_property_free);
- object_init_with_type(obj, type);
- object_post_init_with_type(obj, type);
- }
综合前文(从QEMU源码全解析 —— QOM介绍(1)开始),可以把QOM的对象构造成三部分:第一部分是类型的构造,通过TypeInfo构造一个TypeImpl的哈希表,这是在主函数main之前完成的;第二部分是类型的初始化,这两部分都是全局的,即只要编译进去的QOM对象都会被调用;第三部分是类对象的构造,这是构造具体的对象实例,只有在命令行指定了对应的设备时,才会创建对象。
类比Java中的反射机制:在Java中,对于一个类,首先,在编写代码的时候要写一个class xxx的定义,编译好就放在.class文件中,这是处于纸面的状态。此部分对应上边第一部分 —— 类型的构造;然后,Java会有一个Class对象,用于读取和表示这个纸面上的class xxx。此部分对应上边的第二部分 —— 类型的初始化;最终,Java生成真正的对象。此部分对应上边的第三部分 —— 对象的构造。
再结合实际流程解释一下:class_init函数会生成XXXClass,就相当于Java里边的Class对象;TypeImpl还会有一个instance_init函数,相当于构造函数,用于根据XXXClass生成Object,这就相当于Java反射中最终创建的对象。
欲知后事如何,且看下回分解。