This article introduces the object-oriented modeling mechanism and operation process of the QEMU Object Model (QOM). Taking the edu device modeling source code as an example, it deeply analyzes the three core stages: type registration, type initialization, and object instantiation. It elaborates on how type information constructs a linked list during the loading phase and completes global registration in the main function, as well as the inheritance and interface handling logic during class initialization.
Environment
Source Code
1 2 3 4 5 6
wget https://download.qemu.org/qemu-10.1.2.tar.xz tar xvJf qemu-10.1.2.tar.xz cd qemu-10.1.2 mkdir -p output ./configure --prefix=$PWD/output --target-list=aarch64-softmmu,riscv64-softmmu --enable-debug bear -- make -j$(nproc)
The full name of QOM isQEMU Object Model, which is an abstraction layer implemented by QEMU using object-oriented concepts to organize various components in QEMU (such as device simulation, backend components like MemoryRegion, Machine, etc.). It is similar to C++ classes, but QOM is implemented in pure C language.
The object-oriented features supported by QOM include:Inheritance, Encapsulation, Polymorphism。
The operation process of QOM consists of three parts:
Based on object-oriented modeling concepts, QEMU provides a highly formatted and standardized hardware modeling process. For beginners, it is sufficient to master the common basic interfaces of QOM to smoothly carry out modeling work, without needing to delve into internal principles.
We need to understand three points:
How the device model is defined;
How devices are instantiated during the QEMU loading phase;
How different devices are connected (communicate).
/** * DECLARE_INSTANCE_CHECKER: * @InstanceType: instance struct name * @OBJ_NAME: the object name in uppercase with underscore separators * @TYPENAME: type name * * Direct usage of this macro should be avoided, and the complete * OBJECT_DECLARE_TYPE macro is recommended instead. * * This macro will provide the instance type cast functions for a * QOM type. */ #define DECLARE_INSTANCE_CHECKER(InstanceType, OBJ_NAME, TYPENAME) \ static inline G_GNUC_UNUSED InstanceType * \ OBJ_NAME(const void *obj) \ { return OBJECT_CHECK(InstanceType, obj, TYPENAME); }
/** * OBJECT_CHECK: * @type: The C type to use for the return value. * @obj: A derivative of @type to cast. * @name: The QOM typename of @type * * A type safe version of @object_dynamic_cast_assert. Typically each class * will define a macro based on this type to perform type safe dynamic_casts to * this object type. * * If an invalid object is passed to this function, a run time assert will be * generated. */ #define OBJECT_CHECK(type, obj, name) \ ((type *)object_dynamic_cast_assert(OBJECT(obj), (name), \ __FILE__, __LINE__, __func__))
And type_init calls module_init, whose first parameter isdo_qemu_init_ edu_types(the above DEFINE_The function generated by the TYPE macro will call type_register_static_array), the second parameter isMODULE_INIT_QOM, indicating the QOM initialization type
include/qemu/module.hThe module_init macro in it is as follows
e = g_malloc0(sizeof(*e)); e->init = fn; e->type = type;
l = find_type(type);
QTAILQ_INSERT_TAIL(l, e, node); }
register_module_init() takesThe initialization function of the type, along with its belonging type (MODULE_INIT_QOM for QOM types), constructs a ModuleEntry, then inserts it into the linked list of the corresponding module. The linked lists of all modules are stored in an init_type_list array.
Therefore, all types used by QEMU are uniformly registered into theinit_type_list[MODULE_INIT_QOM]linked list before the main function executes.
type_register_static_array
DEFINE_The type defined by TYPES_init ultimately calls type_register_static_array(type_array, ARRAY_SIZE(type_array)), it calls type for each TypeInfo_register_static, and type_register_static calls type_register_internal, meaning type registration ultimately calls the core functiontype_register_internal()。
the key is type_table_add(ti), which adds the new type to the QOM global type hash table, and later can be used via object_new(“edu”) or OBJECT_CHECK, returning TypeImpl* for upper-layer use.
TypeImpl
TypeImpl stores all information about the type, defined as follows
int num_interfaces; InterfaceImpl interfaces[MAX_INTERFACES]; };
register TypeImpl in the main function
Soon after entering the main function, it uses MODULE_INIT_QOM as a parameter to call the function module_call_init, this function executedinit_type_list[MODULE_INIT_QOM]the init function of each ModuleEntry on the linked list.
Traverse the linked list, call each init function, complete the actual TypeInfo registration (type_register_static_array), add each TypeImpl to the global hash table
do_qemu_init_edu_types call stack
Type initialization
The type registration has been completed earlier, and we have all TypeImpl of QOM registered in the global hash table, but the initialization of the class (type) has not been completed.
The initialization of a class is completed using type_initialize(), and the function’s input is the TypeImpl type ti representing type information.
type_initialize actually executes the class initialization logic, while type_register_internal only creates TypeImpl and adds it to the global table.
The specific function is as follows:
type_initialize
qom/object.c
type_initializeIts main task is toTypeImplthe object (TypeImpl created during the registration phase)truly initialize it into a usable class object, and handle inheritance relationships and interfaces.
if (ti->class) {//Avoid duplicate initialization return; }
// Calculate the size of the class object class_size ti->class_size = type_class_get_size(ti); // Calculate the size of the instance object instance_size ti->instance_size = type_object_get_size(ti); // Calculate the instance alignment instance_align ti->instance_align = type_object_get_align(ti); /* Any type with zero instance_size is implicitly abstract. * This means interface types are all abstract. */ if (ti->instance_size == 0) {// If instance_size is 0, it indicates an abstract type; interface types are also abstract types with no instances ti->abstract = true; } if (type_is_ancestor(ti, type_interface)) { // Perform strict checks on interface types to ensure there are no instance initialization functions and instance-related attributes 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);//Allocate memory for the class object, storing virtual function table, property table, interface list, etc.
parent = type_get_parent(ti); if (parent) { type_initialize(parent);//Recursively initialize the parent class GSList *e; int i;
for (e = parent->class->interfaces; e; e = e->next) {//Initialize parent class interfaces InterfaceClass *iface = e->data; ObjectClass *klass = OBJECT_CLASS(iface);
for (i = 0; i < ti->num_interfaces; i++) {//Initialize current class interfaces: TypeImpl *t = type_get_by_name_noload(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);//Allocate a property hash table for the class to store QOM object property information (e.g., name, size, etc.)
ti->class->type = ti;// The class object stores a pointer to its TypeImpl
while (parent) { if (parent->class_base_init) { parent->class_base_init(ti->class, ti->class_data);//Call the parent class base initialization function } parent = type_get_parent(parent); }
if (ti->class_init) { ti->class_init(ti->class, ti->class_data);//Call the current class's class_init } }
Finally, it will callti->class_init, it will call the edu defined in edu.c_class_init:
1 2 3 4 5 6 7 8 9 10 11 12 13
staticconst TypeInfo edu_types[] = { { .name = TYPE_PCI_EDU_DEVICE,//Device type name .parent = TYPE_PCI_DEVICE,//It inherits from PCI device .instance_size = sizeof(EduState),//Instance size is EduState .instance_init = edu_instance_init,//Instance initialization function .class_init = edu_class_init,//Class initialization function .interfaces = (const InterfaceInfo[]) {//Implement PCI device interface { INTERFACE_CONVENTIONAL_PCI_DEVICE }, { }, }, } };
type_initialize call stack
Type hierarchy
From type_initialize, it can be seen that when a type is initialized, its parent type is also initialized. QOM implements a concept similar to inheritance in C++ through this hierarchy.
The following analysis is based on the edu device as an example:
In the type initialization function type_initialize, it calls ti->class = g_The malloc0(ti->class_size) statement allocates the class structure of the type, which actually represents the type’s information, similar to a class defined in C++.
class_size is a field of TypeImpl. If this type does not specify it, the parent class’s class_size is used for initialization.
The edu device type itself does not define it, so its class_size is TYPE_The value defined in DEVICE, i.e.,sizeof(PCIDeviceClass)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// include/hw/pci/pci_device.h (qemu v9.2.0) structPCIDeviceClass { // First field: some attributes that belong to the "device type" type. DeviceClass parent_class; // Its parent class is ObjectClass (the base of all types).
uint16_t vendor_id; uint16_t device_id; uint8_t revision; uint16_t class_id; uint16_t subsystem_vendor_id; /* only for header type = 0 */ uint16_t subsystem_id; /* only for header type = 0 */
constchar *romfile; /* rom bar */ };
Initialization of the parent class
Below is the relationship diagram among ObjectClass, DeviceClass, and PCIDeviceClass:
It can be seen that they have a containment relationship; in fact, the memory layout compiled by the compiler for C++ inheritance structures is similar to this.
The question arises: when are the member fields of the parent class initialized?
Here it also callstype_initialize, in the previous gdb debugging analysis, g_hash_table_foreach has already been called, so why call it again here? The reason is as follows:
Althoughg_hash_table_foreach()can initialize all types in advance, butnot all types are traversed before machine initialization
object_new may create a type that has not yet beenobject_class_foreach()traversed
For safety, object_new must ensure the type has been initialized
type_initialize is idempotent:
1 2 3
if (ti->class) {//to avoid repeated initialization return; }
/* find driver */ dc = qdev_get_device_class(&driver, errp); if (!dc) { returnNULL; }
/* find bus */ path = qdict_get_try_str(opts, "bus"); if (path != NULL) { bus = qbus_find(path, errp); if (!bus) { returnNULL; } if (!object_dynamic_cast(OBJECT(bus), dc->bus_type)) { error_setg(errp, "Device '%s' can't go on %s bus", driver, object_get_typename(OBJECT(bus))); returnNULL; } } elseif (dc->bus_type != NULL) { bus = qbus_find_recursive(sysbus_get_default(), NULL, dc->bus_type); if (!bus || qbus_is_full(bus)) { error_setg(errp, "No '%s' bus found for device '%s'", dc->bus_type, driver); returnNULL; } }
if (qdev_should_hide_device(opts, from_json, errp)) { if (bus && !qbus_is_hotpluggable(bus)) { error_setg(errp, "Bus '%s' does not support hotplugging", bus->name); } returnNULL; } elseif (*errp) { returnNULL; }
if (migration_is_running()) { error_setg(errp, "device_add not allowed while migrating"); returnNULL; }
/* create device */ dev = qdev_new(driver);
/* Check whether the hotplug is allowed by the machine */ if (phase_check(PHASE_MACHINE_READY) && !qdev_hotplug_allowed(dev, bus, errp)) { goto err_del_dev; }
/* * set dev's parent and register its id. * If it fails it means the id is already taken. */ id = g_strdup(qdict_get_try_str(opts, "id")); if (!qdev_set_id(dev, id, errp)) { goto err_del_dev; }
QOM implements class-based polymorphism similar to C++, an object can be Object, DeviceState, PCIDevice, etc., according to the inheritance hierarchy. In QOM, to facilitate object management, it alsoadds properties to each type and object. Among them:
Class properties exist in the properties field of ObjectClass and are constructed in type_initialize;
Object properties exist in the properties field of Object, which is constructed in object_initialize_with_type;
Both are a hash table(mapping from property names to ObjectProperty).
The addition of attributes is divided into class attribute addition and object attribute addition. Taking object attribute addition as an example, its attribute addition is done through the object_property_add interface.
+----------------+ | ... | +----------------+ | properties +-------------+------------------------------------------------- +----------------+ | | ... | +---+----+ | | | name | | | +--------+ | | | type | +----------------+ +--------+ Object | set +---> property_set_bool +--------+ | get +---> property_get_bool +--------+ | opaque +---------> +-------+ +--------+ | get +--> memfd_backend_get_seal ObjectProperty +-------+ | set +--> memfd_backend_set_seal +-------+ BoolProperty
Introduce two special attributes:
child attribute
Describes the subordinate relationship between objects. The child attribute of the parent object points to the child object. The function to add the child attribute is object_property_add_child:
Represents a connection relationship, indicating that one device references another device. The function to add the link attribute is object_property_add_link :