2.4 QOM介绍
说明:小白学习qemu的一些学习笔记。主要是学习《QEMU&KVM源码解析与应用》这本书。
参考:
《QEMU&KVM源码解析与应用》作者:李强
Qemu - 百问网嵌入式Linux wiki
2.4.3 类型的层次结构
上一节中type_initialize
类型初始化需要先对父类型初始化。本节的层次结构就是为了实现这一功能,QOM
就是通过层次结构实现了类似C++中的继承概念。
edu
设备类型信息的结构体里,开源找到edu
设备的父类型是TYPE_PCI_DEVICE
,说明edu设备是一个pci设备。
hw/misc/edu.c
static const TypeInfo edu_info = {
.name = "edu",
.parent = TYPE_PCI_DEVICE,
};
找TYPE_PCI_DEVICE找到了pci.h
hw/pci/pci.c
static const TypeInfo pci_device_type_info = {
.name = TYPE_PCI_DEVICE,
.parent = TYPE_DEVICE,
};
hw/core/qdev.c
static const TypeInfo device_type_info = {
.name = TYPE_DEVICE,
.parent = TYPE_OBJECT,
};
qom/object.c
static TypeInfo interface_info = {
.name = TYPE_INTERFACE,
.class_size = sizeof(InterfaceClass),
.abstract = true,
};
static TypeInfo object_info = {
.name = TYPE_OBJECT,
.instance_size = sizeof(Object),
.instance_init = object_instance_init,
.abstract = true,
};
在 QEMU 的类型系统(QOM)中,TYPE_OBJECT
是所有类型的根基类:
-
根节点:
TYPE_OBJECT
是 QEMU 类型系统的根,所有类型最终继承自它。 -
直接子类型:图中列出了
TYPE_OBJECT
的 6 个直接子类型,涵盖设备、总线、虚拟机等核心抽象。来自deepseek: 动态性:具体子类型可能因 QEMU 版本、编译配置(如启用的架构或设备)而有所不同。 以下是 TYPE_OBJECT 的直接子类型示意图: TYPE_OBJECT ├── TYPE_DEVICE ## 所有设备基类(如 PCI、ISA 设备) ├── TYPE_BUS ## 总线基类(如 PCI 总线、ISA 总线) ├── TYPE_MACHINE ## 虚拟机类型基类(如 x86、ARM 机器) ├── TYPE_CPU ## CPU 基类(如 x86、ARM CPU) ├── TYPE_INTERFACE ## 接口基类(支持多继承,如 HotplugHandler) └── TYPE_MODULE ## 模块基类(管理动态加载的插件或驱动)
接下来从数据结构角度讨论类型的层次结合。
在类型的初始化函数type_initialize()中,分配了类型的class结构,代表了类型的信息。类似于C++中的一个类。如果本身没有定义class_size
就会使用父类型的class_size进行初始化。下面以edu设备为例分析。
static void type_initialize(TypeImpl *ti)
{
memcpy(ti->class, parent->class, parent->class_size);
}
父类型的成员域是在什么是初始化的呢?
在
type_initialize
中调用下面代码对父类型站的这部分空间进行初始化。static void type_initialize(TypeImpl *ti) { memcpy(ti->class, parent->class, parent->class_size); } ti->class : 目标地址,指向当前类型的类结构内存。 parent->class : 源地址,指向父类的类结构内存。 parent->class_size : 复制的字节数,即父类类结构的大小。
再看type_initialize
函数最后一句
ti->class_init(ti->class, ti->class_data);
ti->class : 分配的`PCIDeviceClass`结构体。
其中,ti->class
对于edu设备而言是一个刚刚分配的PCIDeviceClass
结构体。然而,class_init
回调函数期望的第一个参数类型是ObjectClass
,因此需要进行从ObjectClass
到PCIDeviceClass
的类型转换。
为什么edu_class_init()
,第一个参数设计成ObjectClass
?
- 隐式转换:因父类结构体位于子类起始位置,
PCIDeviceClass*
到ObjectClass*
的转换是内存安全的。- 显式回退:在回调函数中,需通过类型检查宏(如
PCI_DEVICE_CLASS
)将基类指针转换回具体子类指针,以访问子类特有字段。- 设计意义:QOM 通过此机制实现了类似面向对象的继承和多态,同时保持 C 语言的高效性。
转换的原理
内存布局兼容性:
C 语言中,子类结构体的第一个成员必须是父类结构体。例如:
typedef struct PCIDeviceClass { DeviceClass parent_class; // 第一个成员是父类 DeviceClass // PCI 特有字段... } PCIDeviceClass; typedef struct DeviceClass { ObjectClass parent_class; // 第一个成员是基类 ObjectClass // 设备通用字段... } DeviceClass;
因此,
PCIDeviceClass*
的指针可以直接视为DeviceClass*
或ObjectClass*
,无需显式转换。类型安全性:
QEMU 通过类型注册机制确保转换的合法性。例如,PCIDeviceClass
在注册时会声明其父类为TYPE_DEVICE
,从而建立继承链。
关键机制
类型转换宏
QEMU 提供类型检查宏(如PCI_DEVICE_CLASS
),其底层实现为:##define PCI_DEVICE_CLASS(klass) \ OBJECT_CLASS_CHECK(PCIDeviceClass, (klass), TYPE_PCI_DEVICE)
OBJECT_CLASS_CHECK
:验证klass
的类型是否为TYPE_PCI_DEVICE
或其子类。- 若类型匹配,返回
PCIDeviceClass*
指针;否则触发断言错误。多态性的实现
- 父类指针(如
ObjectClass*
)可指向任意子类实例。- 子类通过覆盖父类的函数指针(如
reset
、realize
)实现多态行为。
2.4.4 对象的构造与初始化
本节主要是说明对象初始化。对应的是下图调用instance_init()
。
运行流程为
// 1、从名字找到TypeImpl,进入object_new_with_type
Object *object_new(const char *typename)
{
TypeImpl *ti = type_get_by_name(typename);
return object_new_with_type(ti);
}
// 2、分配对象大小
Object *object_new_with_type(Type type)
{
Object *obj;
g_assert(type != NULL);
// 判断类初始化是否完成
type_initialize(type);
// 对象分配大小
obj = g_malloc(type->instance_size);
object_initialize_with_type(obj, type->instance_size, type);
obj->free = g_free;
return obj;
}
// 对对象初始化
void object_initialize_with_type(void *data, size_t size, TypeImpl *type)
{
Object *obj = data;
g_assert(type != NULL);
type_initialize(type);
g_assert_cmpint(type->instance_size, >=, sizeof(Object));
g_assert(type->abstract == false);
g_assert_cmpint(size, >=, type->instance_size);
memset(obj, 0, type->instance_size);
obj->class = type->class;
object_ref(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);
}
// 递归调用所有父类型的对象初始化函数和自身对象的初始化函数
// 这里是调用父类的instance_init函数
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);
}
}
// 调用instance_post_init完成对象初始化之后的工作
static void object_post_init_with_type(Object *obj, TypeImpl *ti)
{
if (ti->instance_post_init) {
ti->instance_post_init(obj);
}
if (type_has_parent(ti)) {
object_post_init_with_type(obj, type_get_parent(ti));
}
}
QOM 对象构造相关要点
- 类型的构造(注册)
- 方式:通过
TypeInfo
构造一个TypeImpl
的哈希表。 - 时机:在
main
之前完成。
- 方式:通过
- 类型初始化(先调父类构造,再
class_init
)- 进行位置:在
main
函数中。 - 作用范围:全局,所有编译进去的 QOM 对象都会调用。
- 进行位置:在
- 类对象构造和初始化(调
instance_init
)- 构建内容:构造具体的对象实例。
- 触发条件:仅在命令行指定对应设备时,才会创建对象。
- 对象实例化:
- 以上过程,构造出了对象并调用初始化函数,但是
EduState
里面的数据内容并没有填充,这个时候的edu
设备状态并不是可用的 - 对设备而言还需要设置它的
realized
属性为true
才行
- 以上过程,构造出了对象并调用初始化函数,但是
2.4.5 属性
来到了最后一步——实例化:把realized属性设置成true。
object_property_set_bool(OBJECT(dev), true, "realized", &err);
void object_property_set_bool(Object *obj, bool value,
const char *name, Error **errp)
{
QBool *qbool = qbool_from_bool(value);
object_property_set_qobject(obj, QOBJECT(qbool), name, errp);
QDECREF(qbool);
}
void object_property_set_qobject(Object *obj, QObject *value,
const char *name, Error **errp)
{
Visitor *v;
/* TODO: Should we reject, rather than ignore, excess input? */
v = qobject_input_visitor_new(value, false);
object_property_set(obj, v, name, errp);
visit_free(v);
}
void object_property_set(Object *obj, Visitor *v, const char *name,
Error **errp)
{
ObjectProperty *prop = object_property_find(obj, name, errp);
if (prop == NULL) {
return;
}
if (!prop->set) {
error_setg(errp, QERR_PERMISSION_DENIED);
} else {
prop->set(obj, v, name, prop->opaque, errp);
}
}
1. 类属性与对象属性
类别 | 类属性(Class Property) | 对象属性(Object Property) |
---|---|---|
存储位置 | ObjectClass->properties (类型级别) | Object->properties (实例级别) |
初始化时机 | 类型初始化阶段(type_initialize ) | 对象实例化阶段(object_initialize_with_type ) |
用途 | 定义类型的共享元数据或方法(如默认配置) | 管理实例特有状态或参数(如动态配置) |
示例 | 设备类型的realize 方法、desc 描述字段 | 设备的realized 状态、mmio_size 参数 |
继承性 | 支持继承,子类可覆盖父类属性 | 不参与继承,仅作用于当前实例 |
-
类属性:存在于ObjectClass的properties中。是一个哈希表(键:属性名,值:ObjectProperty)。在对象的初始化函数type_initialize中构造
struct ObjectClass { /*< private >*/ Type type; // 类型元信息,用于类型系统和继承关系管理 GSList *interfaces; // 实现的接口链表(支持多接口继承) const char *object_cast_cache[OBJECT_CLASS_CAST_CACHE]; // 对象类型转换缓存(加速向下转型) const char *class_cast_cache[OBJECT_CLASS_CAST_CACHE]; // 类类型转换缓存(加速父类/子类查询) ObjectUnparent *unparent; // 对象与父对象解除关系时的回调函数 GHashTable *properties; // 哈希表,存储类属性(键:属性名,值:ObjectProperty) };
-
对象属性:存放到Object的properties中。是一个哈希表(键:属性名,值:ObjectProperty)。在对象的初始化函数object_initialize_with_type中构造
struct Object { /*< private >*/ ObjectClass *class; // 对象所属的类(类型信息、方法定义) ObjectFree *free; // 析构回调函数,用于释放对象资源(引用计数归零时调用) GHashTable *properties; // 哈希表,存储对象属性(键:属性名,值:ObjectProperty) uint32_t ref; // 引用计数,管理对象生命周期 Object *parent; // 父对象指针,用于构建对象层次关系或设备树 };
上面提到了类属性和对象属性都是存在哈希表里面,key都是属性名,value都是ObjectProperty。属性都是由ObjectProperty来表示的。
typedef struct ObjectProperty
{
gchar *name; // 属性名称(如 "realized"、"mmio_size")
gchar *type; // 属性类型(如 "bool"、"string"、"link")
gchar *description; // 属性描述(用于文档或帮助信息)
ObjectPropertyAccessor *get; // 获取属性值的回调函数
ObjectPropertyAccessor *set; // 设置属性值的回调函数
ObjectPropertyResolve *resolve;// 解析属性依赖或链接的回调函数(可选)
ObjectPropertyRelease *release;// 释放属性资源的回调函数(可选)
void *opaque; // 指向具体属性类型的结构体(如 BoolProperty)
} ObjectProperty;
下面针对void *opaque;
这个指向具体属性类型的结构体,QOM 中不同类型的属性通过具体结构体定义其行为,以下是核心类型:
LinkProperty
用途:
- 管理对象间的父子关系(如设备树中的层级结构)。
- 示例:PCI 设备与其功能部件的链接。
定义于 qom/object.c: typedef struct { Object **child; // 指向子对象的指针(维护对象间的链接关系) void (*check)(const Object *, const char *, Object *, Error **); // 链接有效性检查函数 ObjectPropertyLinkFlags flags; // 链接属性标志(如是否允许动态修改) } LinkProperty; ``` typedef struct { Object **child; // 指向子对象的指针(维护对象间的链接关系) void (*check)(const Object *, const char *, Object *, Error **); // 链接有效性检查函数 ObjectPropertyLinkFlags flags; // 链接属性标志(如是否允许动态修改) } LinkProperty;
StringProperty
用途:
- 管理字符串类型的配置参数或状态(如设备的描述信息)。
- 示例:网卡的
mac_address
属性。typedef struct StringProperty { char *(*get)(Object *, Error **); // 获取字符串值的回调函数 void (*set)(Object *, const char *, Error **); // 设置字符串值的回调函数 } StringProperty;
BoolProperty
用途:
- 管理布尔类型的开关或状态(如设备的启用/禁用状态)。
- 示例:设备的
realized
属性(标记设备是否已初始化)typedef struct BoolProperty { bool (*get)(Object *, Error **); // 获取布尔值的回调函数 void (*set)(Object *, bool, Error **); // 设置布尔值的回调函数 } BoolProperty;
Object
├── properties (GHashTable)
│ └── [属性名] → ObjectProperty
│ ├── name: "realized"
│ ├── type: "bool"
│ ├── set: property_set_bool // 通用设置函数
│ ├── get: property_get_bool // 通用获取函数
│ └── opaque: → BoolProperty // 绑定到具体属性结构体
│ ├── get: edu_get_realized // 具体设备的 get 逻辑
│ └── set: edu_set_realized // 具体设备的 set 逻辑
接下来,以对象属性为例来介绍属性添加和属性设置。
属性添加
就以object_property_set_bool(OBJECT(dev), true, "realized", &err);
为例,在哪里给edu设备添加realized属性的?
设备对对象初始化时,即调用
.instance_init = edu_instance_init,
时,会先调用父类型的初始化调用他们的.instance_init
函数。edu依次向上找:
到这里已经找到在TYPE_DEVICE这个类型的时候,在instance_init里面添加了realized属性。接下来就是获取属性值
device_get_realized
和设置属性device_set_realized
这两个函数
device_get_realized
函数
- 作用:返回设备是否已初始化(
realized
)。- 实现:通常直接返回设备状态字段(如
dev->realized
)。static bool device_get_realized(Object *obj, Error **errp) { DeviceState *dev = DEVICE(obj); return dev->realized; }
device_set_realized
函数
- 作用:设置设备初始化状态。
- 当设置为
true
时,触发设备的realize
方法,完成资源分配和初始化。- 当设置为
false
时,触发unrealize
方法,释放资源。- 实现:根据传入的布尔值调用设备类的
realize
或unrealize
方法。- 这里调用了DeviceClass的realize函数
static void device_set_realized(Object *obj, bool value, Error **errp) { DeviceState *dev = DEVICE(obj); if (value && !dev->realized) { // 调用 realize 方法 if (dc->realize) { dc->realize(dev, &local_err); } } else if (!value && dev->realized) { // 调用 unrealize 方法 device_class_unrealize(dev); } }
属性设置
属性设置的函数主要是object_property_set()
,这里是使用,最后一步一步到属性的set
函数。也就是设置完就会执行属性的set
函数。本小节的例子中代表着device_set_realized
函数。
object_property_set_bool(OBJECT(dev), true, "realized", &err);
最后跑到了device_set_realized
函数,下面就是调用 realize 方法。
父设备的 realized
属性:
-
子设备的实例化需在父设备已实例化的上下文中执行。
-
未调用子类方法的后果:
若父类未显式调用子类的realize
方法,子类逻辑将不会执行,可能导致设备功能缺失。
因此,父类必须负责触发子类初始化。 -
多级继承的扩展性:
在更深的继承层次中(如祖父类→父类→子类),每一级的realize
方法需依次调用下一级的方法,形成链式调用。
下面图示