Qemu学习-QOM模型

QOM模型

Qemu自己提供了一套面向对象编程模型:QOM(QEMU Object Module),可以把其想像为与C++的面向对象编程类似。

why

Qemu中几乎所有的硬件,无论是cpu,总线,内存还是各类device,都是用这套方法实现的,其重要性可见一斑,那么我们为什么要使用QOM呢?一是因为Qemu是一款多架构硬件仿真软件,所以我们需要模拟出不同架构的cpu,这些cpu无疑会有不少共通的地方,也会有很多不同的地方,使用面向对象的方法无疑对模拟这些cpu模型带来极大的便利,类比到各类device也是一个道理,二是因为qemu模拟的是一个完整的计算机,所以只模拟设备是不够的,还需要实现设备间的互通和信息交互,所以为了方便模拟一个device会通过bus与其他的device相连接,一个device上可以通过不同的bus端口连接到其他的device,而其他的device也可以进一步通过bus与其他的设备连接,同时一个bus上也可以连接多个device等情形,使用面向对象的QOM模型是快捷方便的。

how

基础结构体

那么我们如何在qemu中添加一个我们自定义的device呢?官方给出的例子位于hw/misc/pci-testdev.chw/misc/edu.c

四个重要的数据结构:

  • TypeInfo:是用户用来定义一个Type的工具型的数据结构。

  • TypeImpl:对数据类型的抽象数据结构,TypeInfo的属性与TypeImpl的属性对应。

  • ObjectClass:是所有类对象的基类,仅仅保存了一个整数type

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //include/qom/object.h
    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;
    };
  • Object:是所有对象的基类Base Object,第一个成员变量为指向ObjectClass的指针。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //include/qom/object.h
    struct Object
    {
    /*< private >*/
    ObjectClass *class;
    ObjectFree *free;
    GHashTable *properties;
    uint32_t ref;
    Object *parent;
    };

QOM 创建新类型时需要指定其对象类OjectClass和对象Object,这两者通过TypeInfo 结构体指定,基本结构定义在 include/qom/object.h,文件中的注释超级详细,不仅有对数据结构的字段说明和QOM模型的用法,还有很多简单的例子,TypeImpl定义在 qom/object.c中,但是莫得注释。。。

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//include/qom/object.h
typedef struct TypeImpl *Type;
typedef struct TypeInfo TypeInfo;

struct TypeInfo
{
const char *name; //自定义的type的名字
const char *parent;//父类type的名字

size_t instance_size;//object的size
void (*instance_init)(Object *obj);//初始化object的函数
void (*instance_post_init)(Object *obj);
void (*instance_finalize)(Object *obj);

bool abstract;
size_t class_size; //object class的size

void (*class_init)(ObjectClass *klass, void *data); //初始化object class的函数
void (*class_base_init)(ObjectClass *klass, void *data);
void *class_data;

InterfaceInfo *interfaces;
};

//各字段的具体解释
/**
* TypeInfo:
* @name: The name of the type.
* @parent: The name of the parent type.
* @instance_size: The size of the object (derivative of #Object). If
* @instance_size is 0, then the size of the object will be the size of the
* parent object.
* @instance_init: This function is called to initialize an object. The parent
* class will have already been initialized so the type is only responsible
* for initializing its own members.
* @instance_post_init: This function is called to finish initialization of
* an object, after all @instance_init functions were called.
* @instance_finalize: This function is called during object destruction. This
* is called before the parent @instance_finalize function has been called.
* An object should only free the members that are unique to its type in this
* function.
* @abstract: If this field is true, then the class is considered abstract and
* cannot be directly instantiated.
* @class_size: The size of the class object (derivative of #ObjectClass)
* for this object. If @class_size is 0, then the size of the class will be
* assumed to be the size of the parent class. This allows a type to avoid
* implementing an explicit class type if they are not adding additional
* virtual functions.
* @class_init: This function is called after all parent class initialization
* has occurred to allow a class to set its default virtual method pointers.
* This is also the function to use to override virtual methods from a parent
* class.
* @class_base_init: This function is called for all base classes after all
* parent class initialization has occurred, but before the class itself
* is initialized. This is the function to use to undo the effects of
* memcpy from the parent class to the descendants.
* @class_finalize: This function is called during class destruction and is
* meant to release and dynamic parameters allocated by @class_init.
* @class_data: Data to pass to the @class_init, @class_base_init and
* @class_finalize functions. This can be useful when building dynamic
* classes.
* @interfaces: The list of interfaces associated with this type. This
* should point to a static array that's terminated with a zero filled
* element.
*/
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
//qom/object.c
typedef struct TypeImpl TypeImpl;

struct TypeImpl
{
const char *name;

size_t class_size;

size_t instance_size;

void (*class_init)(ObjectClass *klass, void *data);
void (*class_base_init)(ObjectClass *klass, void *data);

void *class_data;

void (*instance_init)(Object *obj);
void (*instance_post_init)(Object *obj);
void (*instance_finalize)(Object *obj);

bool abstract;

const char *parent;
TypeImpl *parent_type;

ObjectClass *class; //初始化Class!!!

int num_interfaces;
InterfaceImpl interfaces[MAX_INTERFACES];
};

自定义一个设备主要分为四个流程:

  1. TypeInfo注册为TypeImpl
  2. 初始化Class
  3. 实例化Object
  4. 添加Property

TypeInfo注册为TypeImpl

用户定义了一个TypeInfo,然后调用 type_register(TypeInfo) 或者 type_register_static(TypeInfo) 函数,就会生成相应的TypeImpl实例,将这个TypeInfo注册到全局的TypeImplhash表中。
TypeInfo的属性与TypeImpl的属性对应,实际上qemu就是通过用户提供的TypeInfo创建的TypeImpl的对象。

include/qom/object.h中第一阶段的例子来看:

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
//include/qom/object.h 
* <example>
* <title>Creating a minimal type</title>
* <programlisting>
* #include "qdev.h"
*
* #define TYPE_MY_DEVICE "my-device"
*
* // No new virtual functions: we can reuse the typedef for the
* // superclass.
* typedef DeviceClass MyDeviceClass;
* typedef struct MyDevice
* {
* DeviceState parent; //父对象必须是该对象数据结构的第一个属性,以便实现父对象向子对象的cast,DeviceState继承自Object,后续会详细解释
*
* int reg0, reg1, reg2;
* } MyDevice;
*
* static const TypeInfo my_device_info = { //用户自定义一个TypeInfo
* .name = TYPE_MY_DEVICE,
* .parent = TYPE_DEVICE,
* .instance_size = sizeof(MyDevice), //Object的size必须注明,以便系统为Object分配内存
* };
*
* static void my_device_register_types(void)
* {
* type_register_static(&my_device_info); //用type_register_static生成相应的TypeImpl实例
* }
*
* type_init(my_device_register_types)//通过type_init宏注册Type
* </programlisting>
* </example>

my_device_info中得字段name定义了我们将来启动此设备时候传参-device后面跟的值。

type_init宏位于include/qemu/module.h,其发生在qemumain函数之前。

1
2
//include/qemu/module.h
#define type_init(function) module_init(function, MODULE_INIT_QOM)

这一过程的目的就是分配并创建TypeImpl结构,使用TypeInfo数据赋值,之后插入到一个hash表之中,这个hash表以ti->name,也就是info->name为key,value就是根据TypeInfo生成的TypeImpl
qemu启动阶段vl.c:main()pci_testdev_register_types()函数将会在module_call_init(MODULE_INIT_QOM)中执行,通过遍历qom队列中的module entry

初始化Class

之后的分析主要根据hw/misc/pci-testdev.c来分析,其中模拟了一个pci-test设备:

继承链:

首先我们要知道ObjectClass是所有类的基类,对于pci设备来说,ObjectClass有以下继承链:

PCIDeviceClass=>DeviceClass=>ObjectClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//include/qom/object.h
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;
};
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
//include/hw/xtensa/qdev-core.h
typedef struct DeviceClass {
/*< private >*/
ObjectClass parent_class;//存储父类型的内容
/*< public >*/

DECLARE_BITMAP(categories, DEVICE_CATEGORY_MAX);
const char *fw_name;//firware名称
const char *desc;
Property *props;//属性,在qemu中,每个设备都有各自的属性,以链表的形式保存在设备的根结构Object的properties字段中。但是每个设备有哪些属性,是在设备的类型中定义的,即这里的properties字段定义设备有哪些属性,设备类型的继承链上每个子类型都有可能定义一些属性,在设备初始化时,会遍历设备类型继承链,把所有属性都存放到Object的属性链表中

/*
* Can this device be instantiated with -device / device_add?
* All devices should support instantiation with device_add, and
* this flag should not exist. But we're not there, yet. Some
* devices fail to instantiate with cryptic error messages.
* Others instantiate, but don't work. Exposing users to such
* behavior would be cruel; clearing this flag will protect them.
* It should never be cleared without a comment explaining why it
* is cleared.
* TODO remove once we're there
*/
bool user_creatable;//设备是否是可以由用户创建的,QEMU中并不是所有模拟的设备都是可以由用户通过命令行创建的,有些设备是要QEMU自动创建的,比如sysbus总线设备
bool hotpluggable;//设备是否是可插拔的

/* callbacks */
DeviceReset reset;//设备复位回调函数
DeviceRealize realize;//设备实例化回调函数。qemu的设备初始化分为两步,一个是设备类型中定义的构造函数(instance_init),创建设备(object_new)时调用,另外一个是这里的realize函数,在设备的realied属性被设置为true时调用,realize函数被调用设备才是真正的被初始化并变的可用
DeviceUnrealize unrealize;//与realize回调函数对应,设备清理时调用

/* device state */
const struct VMStateDescription *vmsd;//该结构体用来保存设备的状态,在虚拟机迁移或冻结时使用

/* Private to qdev / bus. */
const char *bus_type;//总线类型,在qdev的设备模型中,每个设备都有其挂接的总线
} DeviceClass;
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
//include/hw/pci/pci.h
typedef struct PCIDeviceClass {
DeviceClass parent_class;

void (*realize)(PCIDevice *dev, Error **errp);
PCIUnregisterFunc *exit;
PCIConfigReadFunc *config_read;
PCIConfigWriteFunc *config_write;

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 */

/*
* pci-to-pci bridge or normal device.
* This doesn't mean pci host switch.
* When card bus bridge is supported, this would be enhanced.
*/
int is_bridge;

/* rom bar */
const char *romfile;
} PCIDeviceClass;

接着上一大步,现在我们已经有了一个value为TypeImpl的哈希表。下一步就是初始化表中的每个type了,这一步可以看成是Class的初始化,可以理解成每一个type对应了一个class,接下来会初始化Class

具体函数分析:

在初始化每个type时候,调用到的是type_initialize函数。ObjectClass的分配和初始化就在此函数中实现,此外ObjectClassType的关联操作也在此函数中实现。

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
44
45
46
47
48
49
50
51
52
53
54
55
56
/* include/qom/object.h */
struct TypeImpl;
typedef struct TypeImpl *Type;
/* qom/object.c */
struct TypeImpl
{
...
ObjectClass *class; //TypeImpl与ObjectClass的关联,我们就是要初始化这个元素,默认为0
...
};

static void type_initialize(TypeImpl *ti)//负责将type初始化为class
{
TypeImpl *parent;

if (ti->class) { //若type已经被初始化则直接退出
return;
}

ti->class_size = type_class_get_size(ti);//获取class_size,为之后申请内存做准备,这个字段自己可以不填
ti->instance_size = type_object_get_size(ti);//获取instance_size,这个字段自己必须要填
/* Any type with zero instance_size is implicitly abstract.
* This means interface types are all abstract.
*/
if (ti->instance_size == 0) {
ti->abstract = true;
}
if (type_is_ancestor(ti, type_interface)) {
...
}
ti->class = g_malloc0(ti->class_size);//为class申请内存

parent = type_get_parent(ti);
if (parent) { //递归初始化父类type的class
type_initialize(parent);
...
memcpy(ti->class, parent->class, parent->class_size);//将父类的class拷贝到自己的最前面
...
} else {
ti->class->properties = g_hash_table_new_full(
g_str_hash, g_str_equal, g_free, object_property_free);
}

ti->class->type = ti;//将当前type初始化出的class的type指向自己

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);//调用class_init函数,class指针作为第一个参数,初始化class
}
}
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
static TypeImpl *type_get_parent(TypeImpl *type)
{
if (!type->parent_type && type->parent) {
type->parent_type = type_get_by_name(type->parent);
g_assert(type->parent_type != NULL);
}

return type->parent_type;
}

static bool type_has_parent(TypeImpl *type)
{
return (type->parent != NULL);
}

static size_t type_class_get_size(TypeImpl *ti)
{
if (ti->class_size) {//填了直接返回
return ti->class_size;
}

if (type_has_parent(ti)) {
return type_class_get_size(type_get_parent(ti));
}

return sizeof(ObjectClass);//默认返回sizeof(ObjectClass),或为父类的class_size
}

static size_t type_object_get_size(TypeImpl *ti)
{
if (ti->instance_size) {//填了直接返回
return ti->instance_size;
}

if (type_has_parent(ti)) {
return type_object_get_size(type_get_parent(ti));
}

return 0;//默认返回0,或为父类的instance_size,所以在自定义设备时这个位置一定要填
}

type_initialize中,会递归地对TypeImpl中的parent成员(TypeImpl)递归调用type_initialize,然后将创建出来的相应 ObjectClass拷贝到自己class的最前面。

类对象的第一个成员是parent_class,由于父类对象会拷到子类对象的最前面,因此可以认为其指向父类的对象,如此构成链状的继承链,最终指向基类对象ObjectClass

可以发现Type同样存在一条继承链,对于pci设备而言:

TYPE_PCI_TEST_DEV(自定义)=>TYPE_PCI_DEVICE=>TYPE_DEVICE=>TYPE_OBJECT

class_init为我们自己自定义的初始化class的函数,可以类比为C++中的构造函数,其参数为自身,在定义TypeInfo时填入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// hw/misc/pci-testdev.c
static void pci_testdev_class_init(ObjectClass *klass, void *data)
{
//父对象必须是该对象数据结构的第一个属性,以便实现父对象向子对象的cast
DeviceClass *dc = DEVICE_CLASS(klass); //子类转父类的宏
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);//子类转父类的宏

k->realize = pci_testdev_realize; //回调函数,负责初始化Type的ObjectClass实例
k->exit = pci_testdev_uninit;
k->vendor_id = PCI_VENDOR_ID_REDHAT;
k->device_id = PCI_DEVICE_ID_REDHAT_TEST;
k->revision = 0x00;
k->class_id = PCI_CLASS_OTHERS;
dc->desc = "PCI Test Device";
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
dc->reset = qdev_pci_testdev_reset;
}

DEVICE_CLASSPCI_DEVICE_CLASS两个宏由OBJECT_CLASS_CHECK封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* include/hw/qdev-core.h */
#define TYPE_DEVICE "device"
#define DEVICE_CLASS(klass) OBJECT_CLASS_CHECK(DeviceClass, (klass), TYPE_DEVICE)

/* include/hw/pci/pci.h */
#define TYPE_PCI_DEVICE "pci-device"
#define PCI_DEVICE_CLASS(klass) \
OBJECT_CLASS_CHECK(PCIDeviceClass, klass, TYPE_PCI_DEVICE)

/* include/qom/object.h */
/**
* OBJECT_CLASS_CHECK:
* @class_type: The C type to use for the return value.
* @class: A derivative class of @class_type to cast.
* @name: the QOM typename of @class_type.
*
* A type safe version of @object_class_dynamic_cast_assert. This macro is
* typically wrapped by each type to perform type safe casts of a class to a
* specific class type.
*/
#define OBJECT_CLASS_CHECK(class_type, class, name) \
((class_type *)object_class_dynamic_cast_assert(OBJECT_CLASS(class), (name), \
__FILE__, __LINE__, __func__))

流程概况:

1
2
3
4
5
6
7
8
9
10
type_initialize
=> 如果 TypeImpl 已创建(class成员有值),返回
=> ti->class = g_malloc0(ti->class_size) //根据class_size分配内存空间
=> type_get_parent(ti) //获取父类的TypeImpl
=> memcpy(ti->class, parent->class, parent->class_size) //将parent的class拷贝到自己class的最前面
=> ti->class->properties = g_hash_table_new_full //创建存放property的hash table
=> type_initialize_interface //初始化class的接口,包括父类和自己的
=> ti->class->type = ti //设置class的type为对应TypeImpl
=> parent->class_base_init //如果parent定义了class_base_init,调用之
=> ti->class_init(ti->class, ti->class_data) //调用class的class_init

实例化Object

用户定义的PCI TypeObjectClass实例的构造函数调用在type_register_static()调用时即可完成,而TypeObject实例只有在QEMU命令行中添加-device选项时才会创建。
上面已经说到了Type hash table的构造以及Class的初始化,现在来说Object实例的创建。

继承链:

首先还是继承链,对于基础的pci设备来说有以下继承链:

PCIDevice=>DeviceState=>Object

1
2
3
4
5
6
7
8
9
10
//include/qom/object.h
struct Object
{
/*< private >*/
ObjectClass *class; //与ObjectClass联系在一起
ObjectFree *free;
GHashTable *properties;
uint32_t ref;
Object *parent;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//include/hw/xtensa/qdev-core.h
struct DeviceState {
/*< private >*/
Object parent_obj;
/*< public >*/

const char *id;
char *canonical_path;
bool realized;
bool pending_deleted_event;
QemuOpts *opts;
int hotplugged;
BusState *parent_bus;
QLIST_HEAD(, NamedGPIOList) gpios;
QLIST_HEAD(, BusState) child_bus;
int num_child_bus;
int instance_id_alias;
int alias_required_for_version;
};
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//include/hw/pci/pci.h
struct PCIDevice {
DeviceState qdev;

/* PCI config space */
uint8_t *config;

/* Used to enable config checks on load. Note that writable bits are
* never checked even if set in cmask. */
uint8_t *cmask;

/* Used to implement R/W bytes */
uint8_t *wmask;

/* Used to implement RW1C(Write 1 to Clear) bytes */
uint8_t *w1cmask;

/* Used to allocate config space for capabilities. */
uint8_t *used;

/* the following fields are read only */
int32_t devfn;
/* Cached device to fetch requester ID from, to avoid the PCI
* tree walking every time we invoke PCI request (e.g.,
* MSI). For conventional PCI root complex, this field is
* meaningless. */
PCIReqIDCache requester_id_cache;
char name[64];
PCIIORegion io_regions[PCI_NUM_REGIONS];
AddressSpace bus_master_as;
MemoryRegion bus_master_container_region;
MemoryRegion bus_master_enable_region;

/* do not access the following fields */
PCIConfigReadFunc *config_read;
PCIConfigWriteFunc *config_write;

/* Legacy PCI VGA regions */
MemoryRegion *vga_regions[QEMU_PCI_VGA_NUM_REGIONS];
bool has_vga;

/* Current IRQ levels. Used internally by the generic PCI code. */
uint8_t irq_state;

/* Capability bits */
uint32_t cap_present;

/* Offset of MSI-X capability in config space */
uint8_t msix_cap;

/* MSI-X entries */
int msix_entries_nr;

/* Space to store MSIX table & pending bit array */
uint8_t *msix_table;
uint8_t *msix_pba;
/* MemoryRegion container for msix exclusive BAR setup */
MemoryRegion msix_exclusive_bar;
/* Memory Regions for MSIX table and pending bit entries. */
MemoryRegion msix_table_mmio;
MemoryRegion msix_pba_mmio;
/* Reference-count for entries actually in use by driver. */
unsigned *msix_entry_used;
/* MSIX function mask set or MSIX disabled */
bool msix_function_masked;
/* Version id needed for VMState */
int32_t version_id;

/* Offset of MSI capability in config space */
uint8_t msi_cap;

/* PCI Express */
PCIExpressDevice exp;

/* SHPC */
SHPCDevice *shpc;

/* Location of option rom */
char *romfile;
bool has_rom;
MemoryRegion rom;
uint32_t rom_bar;

/* INTx routing notifier */
PCIINTxRoutingNotifier intx_routing_notifier;

/* MSI-X notifiers */
MSIVectorUseNotifier msix_vector_use_notifier;
MSIVectorReleaseNotifier msix_vector_release_notifier;
MSIVectorPollNotifier msix_vector_poll_notifier;
};

Object继承类之间的转换依然是靠宏来实现:

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
/* include/hw/qdev-core.h */
#define TYPE_DEVICE "device"
#define DEVICE(obj) OBJECT_CHECK(DeviceState, (obj), TYPE_DEVICE)

/* include/hw/pci/pci.h */
#define PCI_DEVICE(obj) \
OBJECT_CHECK(PCIDevice, obj, TYPE_PCI_DEVICE)

/* include/qom/object.h */
/**
* 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__))

具体函数分析:

object_new(typename)函数是实例化instance的入口:

1
2
3
4
5
6
7
// qom/object.c
Object *object_new(const char *typename)
{
TypeImpl *ti = type_get_by_name(typename); //用typename去哈希表中获取对应的TypeImpl指针

return object_new_with_type(ti); //以指向TypeImpl的指针为参数去实例化对象
}

继续看object_new_with_type函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* include/qom/object.h */
struct TypeImpl;
typedef struct TypeImpl *Type;

// qom/object.c
static Object *object_new_with_type(Type type)
{
Object *obj;

g_assert(type != NULL);
type_initialize(type); //先初始化TypeImpl->class,流程参见上一部分

obj = g_malloc(type->instance_size);//申请实例的内存
object_initialize_with_type(obj, type->instance_size, type);//创建instance对象的核心函数,申请出的内存指针作为第一个参数,instance的size作为第二个参数,指向TypeImpl的指针type作为第三个参数
obj->free = g_free;

return obj;
}

继续看object_initialize_with_type函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// qom/object.c
static void object_initialize_with_type(void *data, size_t size, TypeImpl *type)
{
Object *obj = data;//obj指向申请出来的用于存放instance的内存空间,size为TypeImpl->instance_size

g_assert(type != NULL);
type_initialize(type);//再次初始化class,应该是个检测的步骤,已经初始化了这里会直接退出

g_assert(type->instance_size >= sizeof(Object));
g_assert(type->abstract == false);
g_assert(size >= type->instance_size);

memset(obj, 0, type->instance_size);//清空脏数据
obj->class = type->class; //此字段为instance与其对应class的联系
object_ref(obj);
obj->properties = g_hash_table_new_full(g_str_hash, g_str_equal,
NULL, object_property_free);//创建存放类实例对象property的hash table
object_init_with_type(obj, type); //一个的迭代函数
object_post_init_with_type(obj, type);
}

继续看object_init_with_type函数:

1
2
3
4
5
6
7
8
9
10
11
// qom/object.c
static void object_init_with_type(Object *obj, TypeImpl *ti)
{
if (type_has_parent(ti)) { //若存在parent,则递归调用object_init_with_type初始化所有parent的instance
object_init_with_type(obj, type_get_parent(ti));
}

if (ti->instance_init) {
ti->instance_init(obj); //若定义了ti->instance_init函数,则调用其来为instance做初始化
}
}

instance的初始化的核心过程到这就结束了,虽然instance_init是字面意义上的实例构造函数,但实际上,经过instance_init函数初始化设备对应instance后,这个instance是不能直接使用的。其真正初始化逻辑的大头都放在realize(如:k->realize = pci_testdev_realize)中做,比如创建对应的memory region,挂载到对应bus上等等。只有在realize后,设备才算真正构造完成,可以拿来使用,所以实例的创建主要由realize函数实现,且不同于typeclass的构造,instance是根据实际需要创建的,只有在命令行指定了设备或者是热插一个设备之后才会有实例的创建。

realize函数是设备实例化callback/回调函数。qemu的设备初始化分为两步,一个是设备类型中定义的构造函数(instance_init),创建设备(object_new)时调用,另外一个是这里的realize函数,在设备的realied属性被设置为true时调用,realize函数被调用设备才是真正的被初始化并变的可用。

此外需要留意classinstance之间是通过instanceclass字段联系在一起的。

流程概况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
main => configure_accelerator => accel_init_machine(acc, ms)
=> ObjectClass *oc = OBJECT_CLASS(acc) //将AccelClass指针转换成父类(ObjectClass)指针
=> object_class_get_name //获取ObjectClass->TypeImpl的类名,如kvm-accel
=> ACCEL(object_new(cname)) //利用名称创建AccelState对象
=> acc->init_machine(ms) //初始化machine,实际上是调用kvm_init

object_new
=> type_get_by_name(typename) //根据类名查type_table获取TypeImpl
=> object_new_with_type => type_initialize //创建TypeImpl对应的类对象,设置到对应TypeImpl->class中
=> g_malloc(type->instance_size) //分配类实例对象的内存
=> object_initialize_with_type //创建类实例对象
=> type_initialize //会再次尝试实例化类对象
=> obj->class = type->class //设置类实例对象的类对象为TypeImpl->class
=> obj->properties = g_hash_table_new_full //创建存放类实例对象property的hash table
=> object_init_with_type => object_init_with_type //如果TypeImpl有父类,递归调用object_init_with_type
=> ti->instance_init(obj) //如果定义了类实例的构造函数,调用之

添加Property

QEMU中每个对象或设备都有一些属性,这些属性有可能是设备的某种状态,也有可能是某个标志等。设备的属性是通过其类型(ObjectClassproperties成员)来定义的,但是具体的属性的值是保存在对象Objectproperties成员的。

关系整理

主要来梳理一下TypeInfoTypeImplObjectClassObject之间的关系,因为TypeImpl是中间工具变量,所以将其与TypeInfo看为一类:

主要有三组:

最基础组,是所有设备的起点:

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
static TypeInfo object_info = {
.name = TYPE_OBJECT,
.instance_size = sizeof(Object),
.instance_init = object_instance_init,
.abstract = true,
};

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;
};

struct Object
{
/*< private >*/
ObjectClass *class;
ObjectFree *free;
GHashTable *properties;
uint32_t ref;
Object *parent;
};

二组,基础设备组:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// hw/core/qdev.c
static const TypeInfo device_type_info = {
.name = TYPE_DEVICE,
.parent = TYPE_OBJECT,//继承自一组的TypeInfo:object_info
.instance_size = sizeof(DeviceState),//与DeviceState的联系
.instance_init = device_initfn,
.instance_post_init = device_post_init,
.instance_finalize = device_finalize,
.class_base_init = device_class_base_init,
.class_init = device_class_init,
.abstract = true,
.class_size = sizeof(DeviceClass),//与DeviceClass的联系
};

//include/hw/xtensa/qdev-core.h
typedef struct DeviceClass {
/*< private >*/
ObjectClass parent_class;//存储父类型的内容
/*< public >*/

DECLARE_BITMAP(categories, DEVICE_CATEGORY_MAX);
const char *fw_name;//firware名称
const char *desc;
Property *props;//属性,在qemu中,每个设备都有各自的属性,以链表的形式保存在设备的根结构Object的properties字段中。但是每个设备有哪些属性,是在设备的类型中定义的,即这里的properties字段定义设备有哪些属性,设备类型的继承链上每个子类型都有可能定义一些属性,在设备初始化时,会遍历设备类型继承链,把所有属性都存放到Object的属性链表中

/*
* Can this device be instantiated with -device / device_add?
* All devices should support instantiation with device_add, and
* this flag should not exist. But we're not there, yet. Some
* devices fail to instantiate with cryptic error messages.
* Others instantiate, but don't work. Exposing users to such
* behavior would be cruel; clearing this flag will protect them.
* It should never be cleared without a comment explaining why it
* is cleared.
* TODO remove once we're there
*/
bool user_creatable;//设备是否是可以由用户创建的,QEMU中并不是所有模拟的设备都是可以由用户通过命令行创建的,有些设备是要QEMU自动创建的,比如sysbus总线设备
bool hotpluggable;//设备是否是可插拔的

/* callbacks */
DeviceReset reset;//设备复位回调函数
DeviceRealize realize;//设备实例化回调函数。qemu的设备初始化分为两步,一个是设备类型中定义的构造函数(instance_init),创建设备(object_new)时调用,另外一个是这里的realize函数,在设备的realied属性被设置为true时调用,realize函数被调用设备才是真正的被初始化并变的可用
DeviceUnrealize unrealize;//与realize回调函数对应,设备清理时调用

/* device state */
const struct VMStateDescription *vmsd;//该结构体用来保存设备的状态,在虚拟机迁移或冻结时使用

/* Private to qdev / bus. */
const char *bus_type;//总线类型,在qdev的设备模型中,每个设备都有其挂接的总线
} DeviceClass;

//include/hw/xtensa/qdev-core.h
struct DeviceState {
/*< private >*/
Object parent_obj;
/*< public >*/

const char *id;
char *canonical_path;
bool realized;
bool pending_deleted_event;
QemuOpts *opts;
int hotplugged;
BusState *parent_bus;
QLIST_HEAD(, NamedGPIOList) gpios;
QLIST_HEAD(, BusState) child_bus;
int num_child_bus;
int instance_id_alias;
int alias_required_for_version;
};

三组,pci基础设备组:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// hw/pci/pci.c
static const TypeInfo pci_device_type_info = {
.name = TYPE_PCI_DEVICE,
.parent = TYPE_DEVICE, //继承自二组的TypeInfo:device_type_info
.instance_size = sizeof(PCIDevice),//与PCIDevice的联系
.abstract = true,
.class_size = sizeof(PCIDeviceClass),//与PCIDeviceClass的联系
.class_init = pci_device_class_init,
.class_base_init = pci_device_class_base_init,
};

//include/hw/pci/pci.h
typedef struct PCIDeviceClass {
DeviceClass parent_class;

void (*realize)(PCIDevice *dev, Error **errp);
PCIUnregisterFunc *exit;
PCIConfigReadFunc *config_read;
PCIConfigWriteFunc *config_write;

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 */

/*
* pci-to-pci bridge or normal device.
* This doesn't mean pci host switch.
* When card bus bridge is supported, this would be enhanced.
*/
int is_bridge;

/* rom bar */
const char *romfile;
} PCIDeviceClass;

//include/hw/pci/pci.h
struct PCIDevice {
DeviceState qdev;

/* PCI config space */
uint8_t *config;

/* Used to enable config checks on load. Note that writable bits are
* never checked even if set in cmask. */
uint8_t *cmask;

/* Used to implement R/W bytes */
uint8_t *wmask;

/* Used to implement RW1C(Write 1 to Clear) bytes */
uint8_t *w1cmask;

/* Used to allocate config space for capabilities. */
uint8_t *used;

/* the following fields are read only */
int32_t devfn;
/* Cached device to fetch requester ID from, to avoid the PCI
* tree walking every time we invoke PCI request (e.g.,
* MSI). For conventional PCI root complex, this field is
* meaningless. */
PCIReqIDCache requester_id_cache;
char name[64];
PCIIORegion io_regions[PCI_NUM_REGIONS];
AddressSpace bus_master_as;
MemoryRegion bus_master_container_region;
MemoryRegion bus_master_enable_region;

/* do not access the following fields */
PCIConfigReadFunc *config_read;
PCIConfigWriteFunc *config_write;

/* Legacy PCI VGA regions */
MemoryRegion *vga_regions[QEMU_PCI_VGA_NUM_REGIONS];
bool has_vga;

/* Current IRQ levels. Used internally by the generic PCI code. */
uint8_t irq_state;

/* Capability bits */
uint32_t cap_present;

/* Offset of MSI-X capability in config space */
uint8_t msix_cap;

/* MSI-X entries */
int msix_entries_nr;

/* Space to store MSIX table & pending bit array */
uint8_t *msix_table;
uint8_t *msix_pba;
/* MemoryRegion container for msix exclusive BAR setup */
MemoryRegion msix_exclusive_bar;
/* Memory Regions for MSIX table and pending bit entries. */
MemoryRegion msix_table_mmio;
MemoryRegion msix_pba_mmio;
/* Reference-count for entries actually in use by driver. */
unsigned *msix_entry_used;
/* MSIX function mask set or MSIX disabled */
bool msix_function_masked;
/* Version id needed for VMState */
int32_t version_id;

/* Offset of MSI capability in config space */
uint8_t msi_cap;

/* PCI Express */
PCIExpressDevice exp;

/* SHPC */
SHPCDevice *shpc;

/* Location of option rom */
char *romfile;
bool has_rom;
MemoryRegion rom;
uint32_t rom_bar;

/* INTx routing notifier */
PCIINTxRoutingNotifier intx_routing_notifier;

/* MSI-X notifiers */
MSIVectorUseNotifier msix_vector_use_notifier;
MSIVectorReleaseNotifier msix_vector_release_notifier;
MSIVectorPollNotifier msix_vector_poll_notifier;
};

用一张我自己画的图片来整理一下在动态初始化时TypeImplObjectClassObject之间的关系:(PS:ObjectObjectClass里的指针是从其class元素发出的)

image-20200808111831263

感悟&参考

QOM是一个很庞大的工程,虽然只学习了一点皮毛,但是已经借鉴了很多前辈的文章,这里只是浅显地分析了一下pcidevice相关的一小部分,还有bus相关的部分有时间再填坑,但这也只是qemu这个庞大软件的一小部分而已。。。不禁感叹自己的弱小与无力orz。

吾尝终日而思矣,不如须臾之所学也。

感觉我们在学习qemu相关的知识时一定要利用好其开源的特性,这点十分重要,不懂的地方没有比读源码更快更正确的解决方式了。

借鉴了些前辈的文章,十分感激:

https://www.binss.me/blog/qemu-note-of-qemu-object-model/

https://blog.csdn.net/sungeshilaoda/article/details/97890633

https://blog.csdn.net/sungeshilaoda/article/details/97890633

打赏还是打残,这是个问题