Qom

Reading time ~4 minutes

写在前面

作为QEMU的入门,这篇文章主要是帮我理清楚QEMU中QOM部分内容的。本文主要分成两部分,一部分介绍QOM的主要数据结构和大致的实现,一部分是一些实例,这些实例实际上代码中注释的例子。

QOM主要数据结构的关系

这里借用一张现成的大佬画的图,借助这张图能够更好的理清QOM的各个数据结构。 QOM

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;

    int num_interfaces;
    InterfaceImpl interfaces[MAX_INTERFACES];
};

这个数据结构存储类的基本信息,QEMU中存在一个全局的hash表,储存所有注册的该数据结构的指针。

利用TypeInfo这个结构,注册TypeImpl。TypeInfo结构实际上是和TypeImpl对应的。

struct TypeInfo
{
    const char *name; // 这个类型的名字
    const char *parent;  //这个类型的父类的名字

    size_t instance_size; //对象对应数据结构的size

    // instance如何初始化和最后的垃圾回收
    void (*instance_init)(Object *obj);
    void (*instance_post_init)(Object *obj);
    void (*instance_finalize)(Object *obj);

    bool abstract; //这个类是否是抽象的,也就是是否有虚拟函数
    size_t class_size; //类对应数据结构的size

    // 类如何初始化和最后的垃圾回收
    void (*class_init)(ObjectClass *klass, void *data);
    void (*class_base_init)(ObjectClass *klass, void *data);
    void (*class_finalize)(ObjectClass *klass, void *data);
    void *class_data;

    // 这个类所实现的接口
    InterfaceInfo *interfaces;
};

QOM提供type_register和type_register_static方法,调用这两个方法注册一个Type,需要传进去的参数就是TypeInfo的指针。

TypeImpl *type_register(const TypeInfo *info)
{
    assert(info->parent);
    return type_register_internal(info);
}

TypeImpl *type_register_static(const TypeInfo *info)
{
    return type_register(info);
}

static TypeImpl *type_register_internal(const TypeInfo *info)
{
    TypeImpl *ti;
    ti = type_new(info);

    type_table_add(ti);
    return ti;
}

type_new函数实际就是利用info队impl进行填充。

Object和ObjectClass

Object可以认为是一个属性或者状态的集合,而ObjectClass则是方法或函数指针的集合,所以可以看到CPU的Object结构名称是CPUState。 struct ObjectClass是所有类的基类。

struct ObjectClass
{
    /*< private >*/
    Type type;  /**/
    GSList *interfaces; //该属性是接口链表节点

    const char *object_cast_cache[OBJECT_CLASS_CAST_CACHE]; //用于做cast操作时的缓存
    const char *class_cast_cache[OBJECT_CLASS_CAST_CACHE];

    ObjectUnparent *unparent;
};

这个结构也是所有接口的基类,一个类可以实现多个接口,interface作为链表的head,该类实现的所有接口的InterfaceClass结构构成了链表。

struct Object是所有对象的基对象。

struct Object
{
    /*< private >*/
    ObjectClass *class;  //指向对应的类的数据结构的指针
    ObjectFree *free;    //当引用计数为0时调用
    GHashTable *properties;  //Object中的所有属性的hash表
    uint32_t ref;        //对象的引用计数
    Object *parent;      //指向父对象的指针
};

列出的这些属性都是私有的。properties中的属性才是可被访问和修改的,这个hash表的每个entry是一个key value对,key是属性的名称,value是ObjectProperty结构,这个结构定义如下:

typedef struct ObjectProperty
{
    gchar *name;
    gchar *type;
    gchar *description;
    ObjectPropertyAccessor *get;
    ObjectPropertyAccessor *set;
    ObjectPropertyResolve *resolve;
    ObjectPropertyRelease *release;
    void *opaque;
} ObjectProperty;

在QOM中,利用object_property_add函数可向一个对象中加入属性,这个函数通常在对象的构造函数中调用。同时QOM还提供了其他的get和set函数用于对属性的读写。

ObjectProperty *
object_property_add(Object *obj, const char *name, const char *type,
                    ObjectPropertyAccessor *get,
                    ObjectPropertyAccessor *set,
                    ObjectPropertyRelease *release,
                    void *opaque, Error **errp)
{
    ObjectProperty *prop;
    size_t name_len = strlen(name);

    if (name_len >= 3 && !memcmp(name + name_len - 3, "[*]", 4)) {
        int i;
        ObjectProperty *ret;
        char *name_no_array = g_strdup(name);

        name_no_array[name_len - 3] = '\0';
        for (i = 0; ; ++i) {
            char *full_name = g_strdup_printf("%s[%d]", name_no_array, i);

            ret = object_property_add(obj, full_name, type, get, set,
                                      release, opaque, NULL);
            g_free(full_name);
            if (ret) {
                break;
            }
        }
        g_free(name_no_array);
        return ret;
    }

    if (g_hash_table_lookup(obj->properties, name) != NULL) {
        error_setg(errp, "attempt to add duplicate property '%s'"
                       " to object (type '%s')", name,
                       object_get_typename(obj));
        return NULL;
    }

    prop = g_malloc0(sizeof(*prop));

    prop->name = g_strdup(name);
    prop->type = g_strdup(type);

    prop->get = get;
    prop->set = set;
    prop->release = release;
    prop->opaque = opaque;

    g_hash_table_insert(obj->properties, prop->name, prop);
    return prop;
}

接口

在TypeImpl和TypeInfo中的InterfaceImpl和InterfaceInfo这两个结构是一样,只包含一个指示type的字符串,接口的数据结构是

struct InterfaceClass
{
    ObjectClass parent_class;  //它的父类就是ObjectClass
    /*< private >*/
    ObjectClass *concrete_class;   //实现这个接口的类的指针
    Type interface_type;           //这个interface的类型(TypeImpl*指针),这个属性指示要实现的接口类型。
};

这个数据结构保持了类实际实现接口的方法的指针。 所有对接口都继承自一个特殊的空接口,用于快速检查当前的TypeImpl是否是一个接口。

动态cast

cast赋予了对象多态的能力,QOM中通过object_class_dynamic_cast函数进行动态的cast

ObjectClass *object_class_dynamic_cast(ObjectClass *class,
                                       const char *typename)
{
    ObjectClass *ret = NULL;
    TypeImpl *target_type;
    TypeImpl *type;

    if (!class) {
        return NULL;
    }

    /* A simple fast path that can trigger a lot for leaf classes.  */
    type = class->type;
    if (type->name == typename) {
        return class;
    }

    target_type = type_get_by_name(typename);
    if (!target_type) {
        /* target class type unknown, so fail the cast */
        return NULL;
    }

    if (type->class->interfaces &&
            type_is_ancestor(target_type, type_interface)) { //通过检查是否为type_interface的子孙快速确定是否为接口
        int found = 0;
        GSList *i;

        for (i = class->interfaces; i; i = i->next) {
            ObjectClass *target_class = i->data;

            if (type_is_ancestor(target_class->type, target_type)) {
                ret = target_class;
                found++;
            }
         }

        /* The match was ambiguous, don't allow a cast */
        if (found > 1) {
            ret = NULL;
        }
    } else if (type_is_ancestor(type, target_type)) {
        ret = class;
    }

    return ret;
}

QOM的使用实例

创建一个新的类

这个类继承自DeviceClass

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

    int reg0, reg1, reg2;
} MyDevice;

static const TypeInfo my_device_info = {
    .name = TYPE_MY_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(MyDevice),
};
static void my_device_register_types(void)
{
    type_register_static(&my_device_info);
}

type_init(my_device_register_types)

DEFINE_TYPES宏可以用来方便的注册多个typeinfo

static const TypeInfo device_types_info[] = {
    {
        .name = TYPE_MY_DEVICE_A,
        .parent = TYPE_DEVICE,
        .instance_size = sizeof(MyDeviceA),
    },
    {
        .name = TYPE_MY_DEVICE_B,
        .parent = TYPE_DEVICE,
        .instance_size = sizeof(MyDeviceB),
    },
};

定义三个宏方便进行cast

#define MY_DEVICE_GET_CLASS(obj) \
        OBJECT_GET_CLASS(MyDeviceClass, obj, TYPE_MY_DEVICE)
#define MY_DEVICE_CLASS(klass) \
        OBJECT_CLASS_CHECK(MyDeviceClass, klass, TYPE_MY_DEVICE)
#define MY_DEVICE(obj) \
        OBJECT_CHECK(MyDevice, obj, TYPE_MY_DEVICE)

类的初始化

这里的类的初始化是指类本身的初始化,而不是对象的初始化,类的初始化只会执行一次,并且保证一个类在执行初始化函数前,他的所有父类均被初始化。类的初始化化函数中可以对父类的虚函数进行重载。

#include "qdev.h"

void my_device_class_init(ObjectClass *klass, void *class_data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    dc->reset = my_device_reset;
}

static const TypeInfo my_device_info = {
    .name = TYPE_MY_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(MyDevice),
    .class_init = my_device_class_init, /*在类初始化时就会调用这个函数,将虚拟函数赋值*/
};

添加新的虚函数则需要类定义自己的结构并给TypeInfo中的class_size变量赋值,每个函数最好有一个wrapper函数方便调用。

#include "qdev.h"

typedef struct MyDeviceClass
{
    DeviceClass parent;
    void (*frobnicate) (MyDevice *obj);
} MyDeviceClass;

static const TypeInfo my_device_info = {
    .name = TYPE_MY_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(MyDevice),
    .abstract = true, // or set a default in my_device_class_init
    .class_size = sizeof(MyDeviceClass),
};

void my_device_frobnicate(MyDevice *obj)
{
    MyDeviceClass *klass = MY_DEVICE_GET_CLASS(obj);
    klass->frobnicate(obj);
}

接口

接口是一个特殊的类,只有class而没有object,class中包含实现方法的函数指针。

重载函数

在QOM中,所有的函数实际上都是虚函数,可以被重载的,如果一个类重载了一个函数,之后对象调用该方法时会直接调用重载的函数,所以类有责任检查是否应该调用原函数。

typedef struct MyState MyState;

typedef void (*MyDoSomething)(MyState *obj);

typedef struct MyClass {
    ObjectClass parent_class;

    MyDoSomething do_something;
} MyClass;

static void my_do_something(MyState *obj)
{
    // do something
}

static void my_class_init(ObjectClass *oc, void *data)
{
    MyClass *mc = MY_CLASS(oc);

    mc->do_something = my_do_something;
}

static const TypeInfo my_type_info = {
    .name = TYPE_MY,
    .parent = TYPE_OBJECT,
    .instance_size = sizeof(MyState),
    .class_size = sizeof(MyClass),
    .class_init = my_class_init,
};

typedef struct DerivedClass {
    MyClass parent_class;

    MyDoSomething parent_do_something;
} DerivedClass;

static void derived_do_something(MyState *obj)
{
    DerivedClass *dc = DERIVED_GET_CLASS(obj);

    // do something here
    dc->parent_do_something(obj);
    // do something else here
}

static void derived_class_init(ObjectClass *oc, void *data)
{
    MyClass *mc = MY_CLASS(oc);
    DerivedClass *dc = DERIVED_CLASS(oc);

    dc->parent_do_something = mc->do_something;
    mc->do_something = derived_do_something;
}

static const TypeInfo derived_type_info = {
    .name = TYPE_DERIVED,
    .parent = TYPE_MY,
    .class_size = sizeof(DerivedClass),
    .class_init = derived_class_init,
};

QEMU 4.1与KVM的初始化与运行循环

## 版本QEMU版本:4.1.0KVM/Linux kernel版本:4.20.17## QEMU/KVM初始化过程,QEMU部分![](images/QEMU/init.png)vl.c文件是QEMU的main函数所在的文件,main函数是qemu的入口,在函数的开始会...… Continue reading

KPTI对VMI的影响

Published on December 11, 2019

Antifuzz

Published on August 29, 2019