qemu学习笔记:QOM
2.4 QOM介绍
说明:小白学习qemu的一些学习笔记。主要是学习《QEMU&KVM源码解析与应用》这本书。
参考:
《QEMU&KVM源码解析与应用》作者:李强
Qemu - 百问网嵌入式Linux wiki
- QOM 定义:QEMU Object Model,是 QEMU 中对对象的抽象层,用于管理和抽象资源(如设备创建、配置、销毁)及后端组件(如 MemoryRegion、Machine 等)。
- 面向对象思想:继承、封装、多态。尽管 QEMU 和 Linux 内核是 C 语言项目,仍充分运用这些思想,QOM 是 QEMU 中具体体现。
- C 语言实现对象:通过父类抽象具体对象(如网卡、显卡等)。例:PCI 设备类的父类是设备类,体现继承。
- 类型与对象区别
- 类型:表示 “种类”(如
edu
是种类)。 - 对象:表示该种类中的具体实例(如
edu1
、edu2
是edu
类型的具体对象)。
- 类型:表示 “种类”(如
- QOM 组成部分
- 类型的注册
- 类型的初始化
- 对象的初始化
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 类型注册 │ → │ 类型初始化 │ → │ 对象初始化 │
└───────────────┘ └───────────────┘ └───────────────┘
该框图表示 QOM 的核心流程,从类型注册开始,经过类型初始化,最终完成对象初始化,体现 QOM 对对象管理的逻辑顺序。

2.4.1 类型的注册
书上的内容就不进行复制了,主要关注的是代码。这一小节主要是type_init()完成注册的过程。先对type_init()进行追踪。介绍了从信息填写到注册再到qemu运行时进入main()之后完成注册过程。就是分析下面这段代码的过程。
static void pci_edu_register_types(void)
{// 信息填写static const TypeInfo edu_info = {.name = "edu",.parent = TYPE_PCI_DEVICE,.instance_size = sizeof(EduState),.instance_init = edu_instance_init,.class_init = edu_class_init,};type_register_static(&edu_info);
}
// 注册
type_init(pci_edu_register_types)
从type_init()开始分析,这是一个宏,用于调用module_init()的时候区分不同的类型
/*
block_init: 块设备(如磁盘等存储相关模块)初始化相关的函数,与 MODULE_INIT_BLOCK 类型绑定
opts_init: 选项(如命令行参数解析、配置选项处理等模块)初始化相关
qapi_init: QAPI(QEMU Abstract Interface,QEMU 抽象接口,用于定义命令、数据结构等)
type_init: QOM(QEMU Object Model,QEMU 对象模型,用于抽象和管理设备、资源等)相关
trace_init: 跟踪(如调试跟踪、事件跟踪等模块)相关
*/
#define block_init(function) module_init(function, MODULE_INIT_BLOCK)
#define opts_init(function) module_init(function, MODULE_INIT_OPTS)
#define qapi_init(function) module_init(function, MODULE_INIT_QAPI)
#define type_init(function) module_init(function, MODULE_INIT_QOM)
#define trace_init(function) module_init(function, MODULE_INIT_TRACE)
module_init()也是一个宏,跳转到register_module_init()完成注册
/* This should not be used directly. Use block_init etc. instead. */
#define module_init(function, type) \
static void __attribute__((constructor)) do_qemu_init_ ## function(void) \
{ \register_module_init(function, type); \
}
#endiftypedef struct ModuleEntry
{void (*init)(void); // 类型的初始化函数 这里就是 pci_edu_register_typesQTAILQ_ENTRY(ModuleEntry) node; // 双向链表module_init_type type; // 类型 这里就是MODULE_INIT_QOM
} ModuleEntry;// 返回init_type_list数组 MODULE_INIT_QOM类型对应的一项
static ModuleTypeList *find_type(module_init_type type)
{init_lists();return &init_type_list[type];
}void register_module_init(void (*fn)(void), module_init_type type)
{ModuleEntry *e;ModuleTypeList *l;e = g_malloc0(sizeof(*e));e->init = fn;e->type = type;// 完成ModuleEntry结构体设置// 获取链表的开头,或者说init_type_list数组 MODULE_INIT_QOM项l = find_type(type);// 链表插入QTAILQ_INSERT_TAIL(l, e, node);
}
这段代码:是register_module_init()函数,把需要注册的类型封装成对应的类型结构体ModuleEntry,插入到对应的链表当中,这些不同类型的链表都在一个叫init_type_list数组里面放着。
下面就是QEMU运行的时候,进入main()后不久,就会调用module_call_init(MODULE_INIT_QOM),遍历MODULE_INIT_QOM对应的链表,挨个执行init()函数,在例子当中就是pci_edu_register_types()函数。
void module_call_init(module_init_type type)
{ModuleTypeList *l;ModuleEntry *e;l = find_type(type);QTAILQ_FOREACH(e, l, node) {e->init();}
}
接下来就看pci_edu_register_types()函数,这个函数首先TypeInfo结构体去承接了一些类型信息。然后将TypeInfo结构体传给type_register_static()函数。
static void pci_edu_register_types(void)
{static const TypeInfo edu_info = {.name = "edu",.parent = TYPE_PCI_DEVICE,.instance_size = sizeof(EduState),.instance_init = edu_instance_init,.class_init = edu_class_init,};type_register_static(&edu_info);
}
又经过封装最后到了type_register_internal()函数,在此次完成两件事
-
type_new():将TypeInfo类型----->TypeImpl类型
-
type_table_add():将 TypeImpl 装入哈希表,以TypeImpl的名字为key,以TypeImpl的本身值为value。
type_register_static(&edu_info);----->type_register_internal()// 完成最终注册,转成TypeImpl加入哈希表
static TypeImpl *type_register_internal(const TypeInfo *info)
{TypeImpl *ti;ti = type_new(info);type_table_add(ti);return ti;
}// 加入哈希表,以名字为key
static void type_table_add(TypeImpl *ti)
{assert(!enumerating_types);g_hash_table_insert(type_table_get(), (void *)ti->name, ti);
}// TypeInfo---》》》TypeImpl
static TypeImpl *type_new(const TypeInfo *info)
{TypeImpl *ti = g_malloc0(sizeof(*ti));int i;g_assert(info->name != NULL);if (type_table_lookup(info->name) != NULL) {fprintf(stderr, "Registering `%s' which already exists\n", info->name);abort();}ti->name = g_strdup(info->name);ti->parent = g_strdup(info->parent);ti->class_size = info->class_size;ti->instance_size = info->instance_size;ti->class_init = info->class_init;ti->class_base_init = info->class_base_init;ti->class_finalize = info->class_finalize;ti->class_data = info->class_data;ti->instance_init = info->instance_init;ti->instance_post_init = info->instance_post_init;ti->instance_finalize = info->instance_finalize;ti->abstract = info->abstract;for (i = 0; info->interfaces && info->interfaces[i].type; i++) {ti->interfaces[i].typename = g_strdup(info->interfaces[i].type);}ti->num_interfaces = i;return ti;
}
到这里,完成了注册过程,类型信息从一开始的TypeInfo类型,转成了TypeImpl,并且加载到了一个哈希表里面,通过TypeImpl的名字可以得到类型的全部信息。在后面的实例化中使用。
2.4.2 类型的初始化
先观察TypeInfo
里面的内容有两个初始化函数,edu_class_init
类初始化和edu_instance_init
实例对象初始化。实例化有两种方式在上面的图中,底层实现顺序基本上一致。
ti->class_init(ti->class, ti->class_data); 类初始化V
ti->instance_init(obj) : 实例对象初始化V
实例化:realized属性设置为ture
static const TypeInfo edu_info = {.name = "edu",.parent = TYPE_PCI_DEVICE,.instance_size = sizeof(EduState),.instance_init = edu_instance_init,.class_init = edu_class_init,};
这一小节主要是针对类型的初始化,分析type_initialize
函数对类型进行初始化:调用ti->class_init(ti->class, ti->class_data)
的过程。
- 在 C++ 等面向对象编程语言中,编译器在声明类型时,能直接知晓类型的相关信息,像对象大小等,这是语言内置的面向对象机制支持。而 C 语言本身不是面向对象语言,没有这种内置支持,要实现类似面向对象特性(如类、对象等概念 ),就得做特殊处理。
- 通过这个函数来手动处理 C 语言中类的初始化过程,弥补 C 语言在面向对象支持上的缺失,实现类型相关信息的设置、内存分配等初始化操作。
type_initialize
函数为了模拟C++的面向对象,主要做了三件事情:
- 设置类结构大小、ti 所代表类型的对象实例的大小。
- 初始化所有父类类型
- 依次调用所有父类的 class_base_init 以及自己的 class_init,这也和 C++ 很类似,在初始化一个对象的时候会依次调用所有父类的构造函数。这里是调用了父类型的 class_base_init 函数。
type_initialize
函数调用场景灵活,但具备 “一次性初始化” 特性。首次调用时,因ti->class
为空(表示类型尚未初始化 ),会执行初始化流程 。后续再调用,ti->class
已被赋值非空,函数直接返回,不再重复初始化过程。这一设计可避免对同一类型多次不必要的初始化,提高程序执行效率,保证类型初始化的一致性和准确性 。
static void type_initialize(TypeImpl *ti)
{TypeImpl *parent;// 先检查ti->class是否存在,若已初始化就直接返回。/*type_initialize函数调用场景灵活,但具备 “一次性初始化” 特性。首次调用时,因ti->class为空(表示类型尚未初始化 ),会执行初始化流程,完成如设置相关尺寸、分配内存、调用父类及自身初始化函数等操作 。后续再调用,ti->class已被赋值非空,函数直接返回,不再重复初始化过程。这一设计可避免对同一类型多次不必要的初始化,提高程序执行效率,保证类型初始化的一致性和准确性 。*/if (ti->class) {return;}// 1、设置类结构大小、ti 所代表类型的对象实例的大小。ti->class_size = type_class_get_size(ti);ti->instance_size = type_object_get_size(ti);ti->class = g_malloc0(ti->class_size);// 2、初始化所有父类类型parent = type_get_parent(ti);if (parent) {type_initialize(parent);GSList *e;int i;g_assert_cmpint(parent->class_size, <=, ti->class_size);memcpy(ti->class, parent->class, parent->class_size);ti->class->interfaces = NULL;ti->class->properties = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, object_property_free);for (e = parent->class->interfaces; e; e = e->next) {InterfaceClass *iface = e->data;ObjectClass *klass = OBJECT_CLASS(iface);type_initialize_interface(ti, iface->interface_type, klass->type);}for (i = 0; i < ti->num_interfaces; i++) {TypeImpl *t = type_get_by_name(ti->interfaces[i].typename);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);}} else {ti->class->properties = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, object_property_free);}ti->class->type = ti;// 3、初始化对象之前需要依次调用父类的构造函数,// 这里是调用父类的class_base_init函数while (parent) {if (parent->class_base_init) {parent->class_base_init(ti->class, ti->class_data);}parent = type_get_parent(parent);}if (ti->class_init) {ti->class_init(ti->class, ti->class_data);}
}
到这里,注册完毕,类型初始化完成,还有对象初始化和实例化没有做。
2.4.3 类型的层次结构
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
才行
- 以上过程,构造出了对象并调用初始化函数,但是