Post

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);
        }
    }
		...
} 
  1. 调用查看parent->class->interfaces, 然后,根据parent interface,来init 当前ti->class->interface
  2. 查看当前的ti->interfaces, 然后和ti->class->interfaces中的每个进行对比, 判断是否在(1)过程中添加过,如果添加过,则不需要再添加(2.1)
  3. 执行到这里,说明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 源码解析与应用»

This post is licensed under CC BY 4.0 by the author.