0%

qemu qom分析

用c语言实现面向对象的模型

qemu里用c语言实现了面向对象的模型。我们先梳理用c实现面向对应的基本逻辑。面向对象
的三个特征是:封装、继承和多态。

封装可以用struct实现。

继承可以用struct包含的方式实现,把父类的struct放到子类struct的最开始的位置,这样
子类的指针可以直接强制转换成父类的指针,在子类的函数,比如子类的初始化函数里可以
直接得到父类的指针,然后调用父类的初始化函数。但是继承层级大于两级的时候似乎是有
问题的(fixme)

多态可以用函数指针的方式实现。

qemu里的实现,多了TypeInfo这个概念,它是class的描述。

类的定义

1
2
3
4
5
6
type_init(fn)
/* 用宏定义了一个动态库的初始化函数, qemu编译出的库有?*/
+-> module_init
+-> register_module_init
/* 如下都用edu设备举例(hw/misc/edu.c),这里的fn就是pci_edu_register_types */
+-> e->init = fn

fn这个函数一般是TypeInfo的注册函数, 把TypeInfo挂到系统的链表里。class是随后解析
Typeinfo的内容动态生成的。

对象的生成

顺着qemu的main函数,看看class和对象是怎么生成的:qemu/softmmu/main.c

1
2
3
4
5
6
7
8
9
10
main
+-> qemu_init
+-> qemu_init_subsystems
/* 根据TypeInfo创建class */
+-> module_call_init(MODULE_INIT_QOM)
/*
* init即为如上的fn, 这里的init只是把TypeInfo向qemu注册,类的
* 初始化还在后面。具体拿edu里的pci_edu_register_types函数看下。
*/
+-> ModuleEntry->init

可以看见这个函数拿自己的初始化函数定义了TypeInfo数据结构,然后把他注册到系统
TypeInfo的链表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}

顺着qemu_init函数继续往下看,找下class以及具体的设备是在哪里创建的。还是以edu
这个设备为例。这个设备使用-device edu的qemu命令行参数启动,所有它创建的位置应该
在:

1
2
qemu_opts_foreach(qemu_find_opts("device"),
device_init_func, NULL, &error_fatal);

下面具体分析其中的qdev_device_add:

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
 qdev_device_add
/* driver就是模块里的device name, edu的driver就是edu */
+-> driver = qemu_opt_get(opts, "driver")
+-> module_object_class_by_name(*driver)
+-> oc = object_class_by_name(typename)
/*
* type_initialize是根据注册的Type创建class的函数。创建class的具体
* 实例的时候,如果class没有创建,就会创建class,被创建的class的指针
* 会放到注册Type的class域段。
*
* 可以看到这个函数为class分配了空间,递归初始化了父类,把父类空间
* 中的内容copy到了当前类最开始的空间。初始化class的interface和
* property,并在最后调用了class的init函数,把class的数据和操作函数
* 都添加上。
*
* 创建的interface class会挂到对应device class的链表上。
*/
+-> type_initialize(type)
/* 注意,这里返回的是DeviceClass */
+-> dc = qdev_get_device_class(&driver, errp)
/* 找见设备对应的bus */
+-> bus = qbus_find(path, errp)
/* 创建设备, 可以看到如果没有class的话,在如下函数里会先创建class */
+-> dev = qdev_new(driver)
+-> object_new(typename)
/*
* 为设备对象分配了内存空间, 把设备里的class指针指向class,为设备
* 初始化class里定义的各个property。调用instance_init初始化设备。
* 注意这个时候设备还不是在可用的状态。
*/
+-> object_new_with_type(ti)
/* 解析输入的设备属性并且保存到设备的属性hash表里 */
+-> qemu_opt_foreach(opts, set_property, dev, errp)
+-> qdev_realize(DEVICE(dev))
/* 调用到class里的realize函数激活设备, 具体的分析在下面一节 */
+-> object_property_set_bool(OBJECT(dev), "realized", true, errp)

properties是什么

所谓属性,就是在一个对象里定义的一些功能,这些功能有名字,有对应的执行函数,还有
添加和删除函数。当添加一个属性的时候,就是把这个属性已经对应的执行函数保存到对象
专门用来存各种属性的一个hash table。当执行属性的操作时,就是执行对应属性附带的
执行函数。

我们还是拿edu这个设备为例。edu在实例初始化的时候挂给PCIDeviceClass的realize一个
回调函数pci_edu_realize,这个函数就是PCI设备里realize属性的执行函数。我们需要明确
这个realize属性在哪里添加和在哪里调用。

device class的初始化函数里增加了realized属性:

1
2
3
4
5
6
7
8
9
/* hw/core/qdev.c */
device_class_init
/*
* 把realized属性加到ObjectClass里。device_set_realized里会调用DeviceClass里的
* realize回调函数。DeviceClass里的realize回调在pci_device_class_init里挂成
* pci_qdev_realize。pci_qdev_realize调用PCIDeviceClass里的realize函数,这个
* 函数又是由具体设备的class init函数添加,比如edu的edu_class_init。
*/
-> object_class_property_add_bool(ObjectClass, "realized", device_get_realized, device_set_realized)

在如上的qdev_device_add里,在创建了设备的实例后,后调用qdev_realize把设备realize,
这个函数会从Device这一层,层层的调用realize函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* hw/core/qdev.c */
qdev_realize
-> object_property_set_bool(OBJECT(dev), "realized", true, errp)
[...]
-> object_property_set
/* 可以看到realized相关的add和find都是发生在Object、ObjectClass这个层次 */
-> ObjectProperty *prop = object_property_find(obj, name, errp)
/* 先用obj找到ObjectClass,再在ObjectClass找property */
-> object_class_property_find(klass, name, NULL)
/* 在Object里找property */
-> g_hash_table_lookup(obj->properties, name)

-> prop->set(obj, v, name, prop->opaque, &err)

如上,分析qemu的qom重点关注如下的文件: hw/misc/edu.c, hw/pci/pci.c, hw/core/qdev.c,
qom/object.c。各个层级的Type定义分别对应的文件里(这里用pci设备为例)

一个典型的使用属性的地方是在qemu启动的时候通过命令行参数给一个设备传递一个属性值。
我们分析这里的代码流程,还是以edu为例,edu_instance_init里的object_property_add_uint64_ptr
为edu设备加了dma_mask这样一个设备属性。在qemu的启动命令行里可以如下配置使用:

1
--device edu,dma_mask=0xffffff

可以看到qemu_opt_foreach(opts, set_property, dev, errp)解析设备属性在instance_init
之后,在realize函数调用之前。所以,edu驱动里在instance_init里把设备属性的定义加
到设备对应的属性hash表里,如上的解析函数才能把命令行输入的属性和设备属性匹配。
edu需要在realize函数或者realize之后才能使用传入的设备属性。

link属性

有了上面的分析,link属性的使用也可以想到,他同样可以使用qemu的启动命令行确定qemu
部件之间的逻辑关系。

child属性

interface

目前只看了PCI/PCIe设备里使用了interface这个东西,PCIe设备用INTERFACE_PCIE_DEVICE
PCI设备用INTERFACE_CONVENTIONAL_PCI_DEVICE。pci设备的realize函数里根据interface
的情况决定是否要使能PCI_CAP_EXPRESS,这个只在PCIe的时候使能。

注意,PCI设备只有0x0~0xff的配置空间。

一个例子

这里写了一个dma engine的qemu设备作为例子