qemu qom
本篇文章,我们主要以edu device 为例,来看一下 qom的框架.
edu device 的代码在
hw/misc/edu.c
简介
QOM 全称 QEMU Object Model, 是QEMU 使用面向对象的方式来进行抽象 设计。面向对象包括封装,继承与多态。而qom就是根据自己自身的需求, 设计的一套面向对象的框架。
面向对象的几个概念:
- 封装: 将数据和操作封装在对象中,隐藏内部细节
- 继承: 子类可以继承父类的属性和方法,重用代码
- 多态: 对象可以根据具体类型表现不同行为,通过相同接口调用不同实现,提高灵活性和可扩展性。
我们会在下面的流程中讲解到qom是如何针对上面三种面向对象的特性进行设计的。
本文主要参考的书籍是
<<QEMU-KVM 源码解析与应用>> 李强
, 该书集写的 很详细,本文主要是根据书中的思路做了一些笔记,以及调试过程。
QOM的class, instance, interface, type, object
我们知道C++有class和Object, class是面向对象的 type, 可以使用具体的 class来创建实例化的object。
而QOM 则有点区别, 增加了一层type,需要使用type来实例化object, 而 type又包含可以包含class,instance和interface,我们来解释下.
class
只是针对某一类object抽象出具有共同特征的封装集合。个人认为主要 是包含了一些方法和该类型的”const public”部分。”const public” 如何理 解呢,例如对于edu设备类型,其
vendor_id
,device_id
都是一样的, 所以就可以把这些成员定义到class中, class一旦 initialize, 就无需在修改。 使用不同的object 实例可以引用一个 class. (当然 方法我们也可以这样认为).instance
而instance不同,instance 是每个object的 private结构,每个object可以根据 自己的信息自定义该部分,例如对于edu device来说, 其
io_region
,irq_state
都属于instance.interface
interface是qom一个比较难琢磨的部分,以下是我个人理解。个人认为interface部分, 实际上体现了QOM中对于多态的面向对象的实现。可以实现用父类class,来调用子类 的function. 当然,子类也可以区别与父类,定义自己的interface。区别于class中的 function,class中的function,必须定位到其具体的class层,例如PCIDeviceClass, 才可以找到其中的
realize()
方法type
type其实才是QOM中的类似于C++的class,其综合了上面三种数据结构。可以用type来 进行object相关操作,例如new, destroy.
object
具体的对象实例.
大家可以想一下,C++做到这些事情是比较简单的,直接按照语法定义好class,编译器会 帮忙做这些事情。而QOM 不同,需要自己实现。
所以, 我们需要关注下, 这些type 是如何注册,并且初始化好的,然后我们在来介绍下, 如何用type来实例化object, 如下图所示:
1
2
3
4
5
6
7
8
9
10
11
12
+--------------+ +------ type_init
|type的注册 +----+------ register_module_init
+--------------+ +------ type_register
+--------------+
|type的初始化 +----------- type_initialize
+--------------+
+--------------+ +------- object_new
|object的初始化+----+------- object_initialize
+--------------+ +------- object_init_with_type
类型注册
1
2
3
4
5
6
7
8
9
10
11
typedef enum {
MODULE_INIT_MIGRATION,
MODULE_INIT_BLOCK,
MODULE_INIT_OPTS,
MODULE_INIT_QOM,
MODULE_INIT_TRACE,
MODULE_INIT_XEN_BACKEND,
MODULE_INIT_LIBQOS,
MODULE_INIT_FUZZ_TARGET,
MODULE_INIT_MAX
} module_init_type;
类型注册相关函数, 早于main执行
1
2
3
4
5
6
Breakpoint 4, do_qemu_init_pci_edu_register_types () at ../hw/misc/edu.c:442
442 type_init(pci_edu_register_types)
(gdb) bt
#0 do_qemu_init_pci_edu_register_types () at ../hw/misc/edu.c:442
#1 0x00007ffff67f3cc4 in __libc_start_main_impl () at /lib64/libc.so.6
#2 0x0000555555884885 in _start ()
init 流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_start
=> __libc_start_main_imp
=> foreach constructor:
{
do_qemu_init_pci_edu_register_types
=> register_module_init
=> alloc ModuleEntry
=> init it
{
e->init
e->type
}
=> link to init_type_list[e->type] list
}
main
=> qemu_init
=> qemu_init_subsystems
=> module_call_init(MODULE_INIT_QOM)
=> foreach_list init_type_list[MODULE_INIT_QOM]
{
e->init(): pci_edu_register_types
}
pci_edu_register_types
1
2
3
4
5
6
7
8
9
10
11
12
13
pci_edu_register_types
=> define : TypeInfo edu_info
=> type_register_static
=> type_register
=> type_register_internal
=> type_new :return TypeImpl* ti
=> {
alloc TypeInfo ti
ti->name = "edu"
ti->parent = "pci-device"
...
}
=> type_table_add
类型初始化
type_initialize
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type_initialize
=> alloc_class {
init: ti->class_size: ## if not define, get parent 's class size
init: ti->instance_size:
ti->class = g_malloc0(ti->class_size) alloc class
}
=> init_class {
如果是祖先interface type, 则需要判断一些东西,
例如instance_size必须是0
abstract 必须是1 等等
=> init_parent_type and copy_class_from_parent
{
=> type_initialize(parent) : ## 递归
=> memcpy(ti->class, parent->class, parent->class_size) ## copy parent class
=> init_interface ## 这块逻辑有点怪,我们在后面贴下具体代码
}
=> init properties: g_hash_table_new_full(g_str_hash, g_str_equal, NULL, object_property_free);
=> ti->class->type = ti ## 设置该class的type
=> while (parent = parent->parent)
parent->class_base_init() ## 递归循环
=> ti->class_init()
}
该部分的逻辑是初始化TypeImpl, 其实主要的是初始化ti->class 和 interface , 在初始化本type的class时, 首先要将class->parent 初始化,以及其 class 的interface初始化.
我们展开下和interface相关的代码, 在看type_initialize
相关代码之前, 我们先看下type_initialize_interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void type_initialize_interface(TypeImpl *ti, TypeImpl *interface_type,
TypeImpl *parent_type)
{
InterfaceClass *new_iface;
TypeInfo info = { };
TypeImpl *iface_impl;
info.parent = parent_type->name;
info.name = g_strdup_printf("%s::%s", ti->name, interface_type->name);
info.abstract = true;
iface_impl = type_new(&info);
iface_impl->parent_type = parent_type;
type_initialize(iface_impl);
g_free((char *)info.name);
new_iface = (InterfaceClass *)iface_impl->class;
new_iface->concrete_class = ti->class;
new_iface->interface_type = interface_type;
ti->class->interfaces = g_slist_append(ti->class->interfaces, new_iface);
}
该函数的作用是, get and init该interface的 TypeImpl
, 需要做的动作大概是:
- init a TypeInfo
- TypeInfo.name = “ti->name::interface_type->name”
Type.parent = parent_type->name
eg
interface_type->name = vmstate-if
ti->name = cpu
interface_type->name = device::vmstate-if
TypeInfo.name = cpu::vmsate-if
TypeInfo.parent = device::vmsate-if
这样来看,是不是就有面向对象编程中,interface的作用了, 如果底层有对interface的实现,则给他覆盖,而且能够实现对父类的覆盖
- alloc TypeImpl : type_new(TypeInfo)
- type_initialize(iface_impl)
- init InterfaceClass
- concrete_clas = ti->class
- interface_type = interface_type
- 将新分配的 interface 加入到
ti->class->interfaces
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static void type_initialize(TypeImpl *ti)
{
...
parent = type_get_parent(ti);
if (parent) {
//===(1)===
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);
if (!t) {
error_report("missing interface '%s' for object '%s'",
ti->interfaces[i].typename, parent->name);
abort();
}
//===(2)===
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;
}
}
//===(2.1)===
if (e) {
continue;
}
//===(3)===
type_initialize_interface(ti, t, t);
}
}
...
}
- 调用查看
parent->class->interfaces
, 然后,根据parent interface,来init 当前ti->class->interface
- 查看当前的
ti->interfaces
, 然后和ti->class->interfaces
中的每个进行对比, 判断是否在(1)过程中添加过,如果添加过,则不需要再添加(2.1) - 执行到这里,说明ti中定义了新的interface,在父类中没有定义过,所以这里
parent_type:arg3
传递的仍然是t
edu class init
1
2
3
4
5
6
7
8
9
10
11
12
13
static void edu_class_init(ObjectClass *class, void *data)
{
DeviceClass *dc = DEVICE_CLASS(class);
PCIDeviceClass *k = PCI_DEVICE_CLASS(class);
k->realize = pci_edu_realize;
k->exit = pci_edu_uninit;
k->vendor_id = PCI_VENDOR_ID_QEMU;
k->device_id = 0x11e8;
k->revision = 0x10;
k->class_id = PCI_CLASS_OTHERS;
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
}
因为edu
这里 初始化 其父类,PCIDeviceClass
和 父类的父类DeviceClass
, 的部分成员。并将k->realize
赋值为pci_edu_realize
下面展示DEVICE_CLASS
宏, 我们看下如果通过当前的ObjectClass
,找到相应的父类
ObjectClass: 实际上是对所有类的抽象,其ObjectClass->type 指向的是当前类 的类型, 我们将此处的class认定为当前class(edu class), device class是其 父类
DEVICE_CLASS 展开, 以edu为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DEVICE_CLASS : this is a static function, declare here:
OBJECT_DECLARE_TYPE(DeviceState, DeviceClass, DEVICE)
OBJECT_DECLARE_TYPE(InstanceType:DeviceState, ClassType:DeviceClass, MODULE_OBJ_NAME:DEVICE)
=> DECLARE_OBJ_CHECKERS(InstanceType:DeviceState, ClassType:DeviceClass, MODULE_OBJ_NAME:DEVICE, TYPE_##MODLE_OBJ_NAME:TYPE_DEVICE)
=> DECLARE_CLASS_CHECKERS(ClassType:DeviceClass, OBJ_NAME:DEVICE, TYPENAME:TYPE_DEVICE)
=> {
static inline DeviceClass * DEVICE_GET_CLASS(const void *obj)
{
return OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE);
}
static inline DeviceClass * DEVICE_CLASS(const void *klass)
{
return OBJECT_CLASS_CHECK(DeviceClass, klass, TYPE_DEVICE);
}
}
#define OBJECT_CLASS_CHECK(class_type, class, name) \
((class_type *)object_class_dynamic_cast_assert(OBJECT_CLASS(class), (name), \
__FILE__, __LINE__, __func__))
(DeviceClass *)object_class_dynamic_cast_assert(Objectclass *class, "device", ...)
可以看到,如果调用DEVICE_CLASS(class)
, 先把class
强转未ObjectClass
, 然后调用 object_class_dynamic_cast_assert
, 然后最终返回DeviceClass *
, 我们来看下具体调用:
1
2
3
4
5
6
7
8
9
10
object_class_dynamic_cast_assert
=> object_class_dynamic_cast
=> type = class->type ## 获取该类的 TypeImpl
=> target_type = type_get_by_name() ## 首先根据typename获取TypeImpl
=> 查看type所在的class是否有interfaces,如果有,查看target_type是否是interface类型
=> Y: 遍历class->interfaces 链表,看看哪个interface是target_type
的子类型(type_is_ancestor),如果是则说明找到了, 可以强转
=> N: 说明target_type不是interface
=> 查看target_type 是不是该type的父类型(type_is_ancestor), 如果是,则说明找到了,可以强转
=> 如果发现可以强转,返回 object, 如果发现不能强转,则返回NULL
所以DEVICE_CLASS
作用是, 判断传入的class查看是否是继承的DeviceClass
, 如果是则进行强转 (DeviceClass *) class
, 如果不是, 则返回NULL
object 初始化
现在注册好了类型,如果我们在qemu命令行执行-device edu
, qemu则会使用前面定义好的类型 来创建一个object
以edu为例,在下面的流程中,会执行对该edu device的初始化:
1
2
3
4
5
6
main
qemu_init
qmp_x_exit_preconfig
qemu_create_cli_devices
qemu_opts_foreach
device_init_func
简单看下device_init_func
流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
device_init_func(QDict *opts, ...) # opts 和字典相关,
=> driver = qdict_get_try_str(opts, "driver") # 通过opts查询 driver, 返回"edu"
=> qdev_get_device_class(&driver, errp) # 通过"edu"找到其class, 转换为DeviceClass
=> path = qdict_get_try_str(opts, "bus"); # 找到bus
=> dev = qdev_new(driver);
=> ObjectClass *oc = object_class_by_name(edu) # 找到edu 的class
=> return DEVICE(object_new(name)); # 调用object_new , 然后强转为DeviceState实例
=> dev->opts = qdict_clone_shallow(opts);
=> object_set_properties_from_keyval()
=> qdev_realize(DEVICE(dev), bus, errp) # 对该device实例化
=> qdev_set_parent_bus(dev, bus, errp); # 设置parent_bus
=> object_property_set_bool(OBJECT(dev), "realized", true, errp);
{
这里先不展开, 在device_class_init, 会通过
object_class_property_add_bool(class, "realized",
device_get_realized, device_set_realized)
注册好"realized" 的get callbak和set callbak
}
=> device_set_realized # 代码比较多, 只看一个地方
=> dc->realize() # 这里回调用到pci_edu_realize
我们主要看下object_new
object new 流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object_new
=> ti = type_get_by_name(typename) # 通过typename,找到TypeImpl
=> object_new_with_type(Type type:ti)
=> type_initialize(type) # 如果该type没有初始化过,在这里初始化
=> alloc object instance
{
size = type->instance_size;
align = type->instance_align;
这里会根据align_size的大小,选择是否对其分配,下面我们展开代码:
obj = g_malloc() / qemu_memalign
obj_free = g_free / qemu_vfree
}
=> object_initialize_with_type(obj, size, type)
=> memset(obj, 0, type->instance_size);
=> obj->class = type->class;
=> object_ref(obj) # org refcount
=> object_class_property_init_all(obj) #和properties相关
=> obj->properties = g_hash_table_new_full()
=> object_init_with_type(obj, type);
=> 递归对obj以及parent 都调用 ti->instance_init()
=> object_post_init_with_type()
=> 递归, 对其 obj 以及parent调用 ti->instance_post_init()
这里需要注意的是,
object_init_with_type()
, 调用ti->instance_init()
时,需要 先对父类进行init,再对子类, 而object_post_init_with_type()
则相反。
看下pci_edu_realize
1
2
3
4
5
6
7
8
pci_edu_realize
=> pci_config_set_interrupt_pin(pci_conf, 1) # 设置interrupt_pin
=> msi_init(pdev, 0, 1, true, false, errp) # 设置msi
=> timer_init_ms(&edu->dma_timer, ...) # dma timer
=> memory_region_init_io(&edu->mmio, &edu_mmio_ops, edu, "edi-mmio", 1* MiB)
# register mmio
=> pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio)
# register bar
经过上面流程,edu 实例已经初始化完成。
qom properties
QOM为了便于管理对象,为每个class定义了properties
1
2
3
4
5
6
struct ObjectClass
{
...
GHashTable *properties;
...
}
每个Property object定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
struct ObjectProperty
{
char *name;
char *type;
char *description;
ObjectPropertyAccessor *get;
ObjectPropertyAccessor *set;
ObjectPropertyResolve *resolve;
ObjectPropertyRelease *release;
ObjectPropertyInit *init;
void *opaque;
QObject *defval;
}
- name: property name,key value
- type: property type, 类型名,例如bool, string, link
- init, get, set, resolve, release 则是注册的回调
- opaque: 指向一个具体的类型(type中指定的), 其内定义了更具体的回调
我们来看下几个具体类型:
1
2
3
4
5
6
7
8
9
10
11
typedef struct BoolProperty
{
bool (*get)(Object *, Error **);
void (*set)(Object *, bool, Error **);
} BoolProperty;
typedef struct StringProperty
{
char *(*get)(Object *, Error **);
void (*set)(Object *, const char *, Error **);
} StringProperty;
以执行下面语句为例, 我们看下:
1
2
3
4
5
6
7
8
device_class_init
...
=> object_class_property_add_bool(class, "realized",
device_get_realized, device_set_realized);
=> object_class_property_add_bool(class, "hotpluggable",
device_get_hotpluggable, NULL);
...
我们来画下edu class 的图:
1
2
3
4
5
6
7
8
9
+--------------+
|ObjectClass |
+--------------+
|DeviceClass |
|其他成员 |---------- PCIDeviceClass, 也就是edu class parent, 但是edu class并没有增加其他member
+--------------+
|PCIDeviceClass|
|其他成员 |
+--------------+
ObjectClass
1
2
3
4
5
6
7
8
9
+--------------------+ +---------------+
|ObjectClass | +------+ |
+--------------------+ | +---------------+
|type:Type(TypeImpl*)+----+
+--------------------+
|interfaces:GSList |
+--------------------+
|properties |
+--------------------+
图:
DeviceClass->properity图示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
+-----------+
|ObjectClass|
+-----------+
| |
+-----------+
|properity +---+------+-------------
+-----------+ | |
| | | |
+-----------+ | |
| |
| +-------+
| |name +--------- "realized"
| +-------+
| |type +--------- "bool"
| +-------+
| |set +--------- properity_set_bool
| +-------+
| |get +--------- properity_get_bool
| +-------+
| |opaque +-------+ +-------------+
| +-------+ +--+BoolProperty |
| +-------------+
| |get +---- device_get_realized
| +-------------+
| |set +---- device_set_realized
+--------+ +-------------+
|
+---+---+
|name +--------- "hotpluggable"
+-------+
|type +--------- "bool"
+-------+
|set +--------- properity_set_bool
+-------+
|get +--------- properity_get_bool
+-------+
|opaque +-----+ +------------+
+-------+ +----+BoolProperty|
+------------+
|get +--- device_set_hotplugable
+------------+
|set +--- NULL
+------------+
init
该成员是一个hash表,在type_initialize()初始化:
1
2
3
4
type_initialize
=> ti->class->properties = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
object_property_free);
g_hash_table_new_full
的参数依次为:
- hash_func: get key hash
- key_equal_func: compare key
- key_destroy_func: free key
- value_destroy_func: free value
add
上面列出来一些,这次,我们列举全:
1
2
3
4
5
6
7
8
9
device_class_init
=> object_class_property_add_bool(class, "realized",
device_get_realized, device_set_realized);
=> object_class_property_add_bool(class, "hotpluggable",
device_get_hotpluggable, NULL);
=> object_class_property_add_bool(class, "hotplugged",
device_get_hotplugged, NULL);
=> object_class_property_add_link(class, "parent_bus", TYPE_BUS,
offsetof(DeviceState, parent_bus), NULL, 0);
以简单的bool类型为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object_class_property_add_bool
=> BoolProperty *prop = g_malloc
=> prop->get = device_get_realized
=> prop->set = device_set_realized
=> object_class_property_add(class, "realized", "bool",
property_get_bool, property_set_bool,
NULL,
void * opaque: prop
)
=> prop = g_malloc0(ObjectProperty)
=> init {
prop->name
prop->type
prop->get, set, release, opaque
}
=> g_hash_table_insert(class->properties, prop->name, prop)
find
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectProperty *object_class_property_find(ObjectClass *klass, const char *name)
{
ObjectClass *parent_klass;
parent_klass = object_class_get_parent(klass);
if (parent_klass) {
ObjectProperty *prop =
object_class_property_find(parent_klass, name);
if (prop) {
return prop;
}
}
return g_hash_table_lookup(klass->properties, name);
}
NOTE: 自己理解
这里搜索
prop
时,有顺序要求么,为什么还要先递归search parent class是因为在进行
type_realized
时,并没有继承property, 理论上也不允许 父类和子类有相同name 的 property, 所以,这里会search parent class, 并且search顺序无所谓
set
1
2
3
4
5
6
object_property_set_bool
=> object_property_set
=> ObjectProperty *prop = object_property_find_err
=> prop->set() # property_set_bool
=> BoolProperty *prop = opaque
=> prop->set() # device_set_realized
流程比较简单,先find, 再set
others
gcc constructor attribute test
编写程序测试:
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
void __attribute__ ((constructor)) before_main()
{
printf("before main\n");
}
int main()
{
printf("main exec \n");
}
执行程序获取输出:
1
2
3
➜ constructor_test ./main
before main
main exec
gdb 调试下:
1
2
3
#0 before_main () at main.c:5
#1 0x00007ffff7df5cc4 in __libc_start_main_impl () from /lib64/libc.so.6
#2 0x0000000000401065 in _start ()
发现在main前, 执行_start
时,会执行constructor之前的函数
参考文献
[1]. «QEMU-KVM 源码解析与应用»