qemu qom模型

读过qemu源码的同学都会发现里面很多Type,object,class这些东西,这就是qemu的qom的组件。如果不能很好的理解qom模型就很难阅读源码。

qemu是c语言写的,但是qemu需要模拟很多复杂的设备总线这些东西,而这些设备总线之间是存在一些共性和继承关系的,也就是需要一些面向对象的描述。为了用c来模拟面向对象的东西就创造出了qom(qemu object model)模型。

在qom模型中有很多类似的概念,如果不加区分很容易搞混。

type,class,xxxstate的区别与联系

在qemu的设备模拟中充斥着xx_info,并有一个type_init函数紧随其后,例如:

static const TypeInfo virt_machine_info = {
    .name          = TYPE_VIRT_MACHINE,
    .parent        = TYPE_MACHINE,
    .abstract      = true,
    .instance_size = sizeof(VirtMachineState),
    .class_size    = sizeof(VirtMachineClass),
    .class_init    = virt_machine_class_init,
    .instance_init = virt_instance_init,
    .interfaces = (InterfaceInfo[]) {
         { TYPE_HOTPLUG_HANDLER },
         { }
    },
};

static void machvirt_machine_init(void)
{
    type_register_static(&virt_machine_info);
}
type_init(machvirt_machine_init);

这是arm64的virt主板的type info,称为virt_machine_info,由type_init来注册。这个type info类似于c++的一个类的概念,但是并不一样,它更像是一个类的描述,因为它并不包含具体的数据成员,只是隐含了静态数据(class所指)和非静态数据(initance所指,也就是xxxstate)。这里最重要的三类概念都出现了,type_info,class,xxxstate。搞清楚这三个东西才能理解qom,如果你用c++的类的模型来理解的话,type_info可以视作类的描述,class可视作静态成员,xxxstate可视作对象。但是这三者在某种程度上是独立的,xxxstate并不是有type info定义的,他们各自有自己的父子节点体系,而且互不干扰。比如virt_machine_type的父类型是machine_type;VirtMachineClass的父类是MachineClass,VirtMachineState的父结构是MachineState。他们之间有联系,但是是平行的对应的。

比如virt_machine_info的类型层次为TYPE_VIRT_MACHINE->TYPE_MACHINE->TYPE_OBJECT。

VirtMachineClass的层次为:VirtMahcineClass->MachineClass->ObjectClass。

VirtMachineState的层次为:VirtMachineState->MachineState->Object。

他们各自有自己的继承关系,在c语言中,这种继承是通过包含来实现的。在子结构体中的第一个元素往往是父结构。

好了,现在我们搞清楚了type,class,state这三者之间的关系,这是了解qom最重要的一步,我看到很多资料都没有讲的很清楚,而这又是非常容易混淆的东西。综合【1】【2】结合我自己的理解,有了上面的论述,当然我不一定是对的,但是我觉得很接近事实。

qom模型的初始化包括三个阶段,类型的注册(type init),静态类型初始化(type initialize),对象初始化(object new)。下面分别讲解。

类型的注册

这里的类型就是上面提到的type。注册是指将需要编译的代码中有关type的描述注册成一系列对应的数据结构并且组织起来,方便后面使用。每个type info之后的type_init宏的作用就是注册。

#define type_init(function) module_init(function, MODULE_INIT_QOM)

#define module_init(function, type)                                         \
static void __attribute__((constructor)) do_qemu_init_ ## function(void)    \
{                                                                           \
    register_module_init(function, type);                                   \
}

type_init最终会调用register_module_init。__attribute__((constructor))说明这个宏是在进入main函数之前调用的。module_init的入参是MODULE_INIT_QOM,代表type都是qom类型的。

static ModuleTypeList *find_type(module_init_type type)
{
    init_lists();

    return &init_type_list[type];
}

void register_module_init(void (*fn)(void), module_init_type type)
{
    ModuleEntry *e;
    ModuleTypeList *l;

    e = g_malloc0(sizeof(*e));
    e->init = fn;
    e->type = type;

    l = find_type(type);

    QTAILQ_INSERT_TAIL(l, e, node);
}

find_type会返回init_type_list中对应qomt类型的链表。init_type_list是一个全局链表数组。对应的type类型会在registe_module_init函数中分配内存,主要是将function设置到init成员,之后挂到qom链表中。

在进入main函数之后,会调用module_call_init来初始化type。调用链为:main->qemu_init->qemu_init_subsystems->module_call_init。

void module_call_init(module_init_type type)
{
    ModuleTypeList *l;
    ModuleEntry *e;

    if (modules_init_done[type]) {
        return;
    }

    l = find_type(type);

    QTAILQ_FOREACH(e, l, node) {
        e->init();
    }

    modules_init_done[type] = true;
}

module_call_init会遍历type_init注册的类型,然后调用他们的init回调函数。对virt_machine_info这个例子,init回调就是machvirt_machine_init,也就是type_init的入参。

一般这个init回调会调用到type_register_internal函数来初始化这个type。

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

    if (!type_name_is_valid(info->name)) {
        fprintf(stderr, "Registering '%s' with illegal type name\n", info->name);
        abort();
    }

    ti = type_new(info);

    type_table_add(ti);
    return ti;
}

type_new会创建一个TypeImpl类型的结构,将type info中的信息复制过去,然后将其加入到哈希表中。

static void type_table_add(TypeImpl *ti)
{
    assert(!enumerating_types);
    g_hash_table_insert(type_table_get(), (void *)ti->name, ti);
}

static GHashTable *type_table_get(void)
{
    static GHashTable *type_table;

    if (type_table == NULL) {
        type_table = g_hash_table_new(g_str_hash, g_str_equal);
    }

    return type_table;
}

这里要注意,type_table是一个静态变量,在第一次分配内存之后就会一直存在,所以在之后的调用中都会返回第一次分配的tpye_table,这样每次调用type_table_add都会加入到一个哈希表中。想要找到这个哈希表也仅仅需要调用type_table_get即可。

这样type info就转变为TypeImpl结构了。之后就可以从TypeImpl结构去查找type。

类型的进一步初始化

上一步仅仅是将type最基本的信息记录下来,下一步要全面初始化type类型。这是在type_initialize函数中做的。对于virt_machine_info的初始化,调用链为main->qemu_init->qemu_create_machine->select_machine->object_class_get_list->object_class_foreach->object_class_foreach_tramp->type_initialize

type_initialize比较长,主要包含两部分,设置相关的成员,给class成员分配内存。

 

static void type_initialize(TypeImpl *ti)
{
    TypeImpl *parent;

    if (ti->class) {
        return;
    }

    ti->class_size = type_class_get_size(ti);
    ti->instance_size = type_object_get_size(ti);
    ti->instance_align = type_object_get_align(ti);
    /* 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)) {
        assert(ti->instance_size == 0);
        assert(ti->abstract);
        assert(!ti->instance_init);
        assert(!ti->instance_post_init);
        assert(!ti->instance_finalize);
        assert(!ti->num_interfaces);
    }
    ti->class = g_malloc0(ti->class_size);

之后会对parent进行初始化。

    parent = type_get_parent(ti);
    if (parent) {
        type_initialize(parent);
        GSList *e;
        int i;

        g_assert(parent->class_size <= ti->class_size);
        g_assert(parent->instance_size <= ti->instance_size);
        memcpy(ti->class, parent->class, parent->class_size);
        ti->class->interfaces = NULL;

        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_noload(ti->interfaces[i].typename);
            if (!t) {
                error_report("missing interface '%s' for object '%s'",
                             ti->interfaces[i].typename, parent->name);
                abort();
            }
            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;
                }
            }

            if (e) {
                continue;
            }

            type_initialize_interface(ti, t, t);
        }
    }

    ti->class->properties = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
                                                  object_property_free);

    ti->class->type = ti;

最后会调用父类型的class_base_init函数和当前类型的class_init函数。

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

上面还会初始化interface等还没有将到的部分内容。

type_initialize会出现在很多地方,但是type只需要初始化一次。

object_class_get_list会遍历所有之前注册的类型,每到一个类型都会调用type_initialize函数进行初始化。

 对象的构造

构造对象就需要调用type对应的非静态成员的初始化函数,对于virt_machine_type,就是调用virt_instance_init。下面是调用virt_instance_init的调用栈。

在qemu_create_machine中会根据命令行传入的machine类型来找到之前注册的对应的type,根据这个type调用object_new_with_class,之后会调用object_new_with_type。这是一个更为基础的函数,使用它的更普遍的方法是调用object_new。

Object *object_new_with_class(ObjectClass *klass)
{
    return object_new_with_type(klass->type);
}

Object *object_new(const char *typename)
{
    TypeImpl *ti = type_get_or_load_by_name(typename, &error_fatal);

    return object_new_with_type(ti);
}

object_new_with_type创建一个Object对象并分配内存之后会做进一步初始化。

static Object *object_new_with_type(Type type)
{
    Object *obj;
    size_t size, align;
    void (*obj_free)(void *);

    g_assert(type != NULL);
    type_initialize(type);

    size = type->instance_size;
    align = type->instance_align;

    /*
     * Do not use qemu_memalign unless required.  Depending on the
     * implementation, extra alignment implies extra overhead.
     */
    if (likely(align <= __alignof__(qemu_max_align_t))) {
        obj = g_malloc(size);
        obj_free = g_free;
    } else {
        obj = qemu_memalign(align, size);
        obj_free = qemu_vfree;
    }

    object_initialize_with_type(obj, size, type);
    obj->free = obj_free;

    return obj;
}

object_initialize_with_type会将object用0填充,初始化property,最后调用object_init_with_type。

static void object_init_with_type(Object *obj, TypeImpl *ti)
{
    if (type_has_parent(ti)) {
        object_init_with_type(obj, type_get_parent(ti));
    }

    if (ti->instance_init) {
        ti->instance_init(obj);
    }
}

object_init_with_type最终调用type指定的instance_init回调。至此Object对象就创建完成了。

前文提到,vrit_machine_info type对应的对象是 VirtMachineState,但是前面只是创建了Object对象,VirtMachineState在哪里?其实Object是所有对象的祖先,因此可以转化为任何子类。在initant_init回调中会将Object转化为VirtMahcine_State并初始化。

static void virt_instance_init(Object *obj)
{
    VirtMachineState *vms = VIRT_MACHINE(obj);
    VirtMachineClass *vmc = VIRT_MACHINE_GET_CLASS(vms);

    /* EL3 is disabled by default on virt: this makes us consistent
     * between KVM and TCG for this board, and it also allows us to
     * boot UEFI blobs which assume no TrustZone support.
     */
    vms->secure = false;

    /* EL2 is also disabled by default, for similar reasons */
    vms->virt = false;

...

所以,调用万initant_init回调,其实我们得到就是最终的VirtMachineState对象。

属性

 属性可以帮助对象实现类似C++多态的性质。属性分为两部分,静态和非静态。静态属性存在于ObjectClass中,非静态存在于Object中,在Object结构体中有一个properties用来存放property。

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

ObjectClass

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

两者都包含了properties域,都是hash类型。

属性类由ObjectProperty表示。

struct ObjectProperty
{
    char *name;
    char *type;
    char *description;
    ObjectPropertyAccessor *get;
    ObjectPropertyAccessor *set;
    ObjectPropertyResolve *resolve;
    ObjectPropertyRelease *release;
    ObjectPropertyInit *init;
    void *opaque;
    QObject *defval;
};

name是属性名,type是属性的类型,有bool,link,string等。每一种属性类都有对应的结构体,opaque用来存放具体的属性类。

typedef struct {
    union {
        Object **targetp;
        Object *target; /* if OBJ_PROP_LINK_DIRECT, when holding the pointer  */
        ptrdiff_t offset; /* if OBJ_PROP_LINK_CLASS */
    };
    void (*check)(const Object *, const char *, Object *, Error **);
    ObjectPropertyLinkFlags flags;
} LinkProperty;

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;

还有相关的函数负责查找添加属性。object_property_add, object_property_find。

对于设备类对象,最重要的属性是realized,它是bool类型。在qemu代码中常常可以看到类似object_property_set_bool(OBJECT(dev), true, "realized", &err); 

 

参考文献

【1】QEMU/KVM源码解析与应用 李强

【2】https://martins3.github.io/qemu/qom.html

【3】https://martins3.github.io/

posted on 2025-05-07 23:03  半山随笔  阅读(114)  评论(0)    收藏  举报

导航