2017-01-08

QOM全称qemu object model,顾名思义,这是对qemu中对象的一个抽象层。通过QOM可以对qemu中的各种资源进行抽象、管理。比如设备模拟中的设备创建,配置,销毁。QOM还用于各种backend的抽象,MemoryRegion,Machine等的抽象,毫不夸张的说,QOM遍布于qemu代码。本文以qemu的设备模拟为例,对QOM进行详细介绍。本文代码基于qemu-2.8。

一. 模块注册

在hw文件目录下的设备模拟中,几乎所有.c文件都会有一个全局的

type_init(xxxxxxxxx)

。这就是向QOM模块注册自己,比如

type_init(serial_register_types)//注册serial
type_init(vmxnet3_register_types)//注册vmxnet3

这类似于Linux驱动模块的注册,在这里type_init是一个宏,在include/qemu/module.h中,我们看到

#define module_init(function, type)                                         \
static void __attribute__((constructor)) do_qemu_init_ ## function(void)    \
{                                                                           \
    register_module_init(function, type);                                   \
}
typedef enum {
    MODULE_INIT_BLOCK,
    MODULE_INIT_OPTS,
    MODULE_INIT_QAPI,
    MODULE_INIT_QOM,
    MODULE_INIT_TRACE,
    MODULE_INIT_MAX
} module_init_type;

#define block_init(function) module_init(function, MODULE_INIT_BLOCK)
#define opts_init(function) module_init(function, MODULE_INIT_OPTS)
#define qapi_init(function) module_init(function, MODULE_INIT_QAPI)
#define type_init(function) module_init(function, MODULE_INIT_QOM)
#define trace_init(function) module_init(function, MODULE_INIT_TRACE)

这里有多个module,对于xxx_init,都是通过调用module_init来进行注册的。

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

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

    return &init_type_list[type];
}

static ModuleTypeList init_type_list[MODULE_INIT_MAX];

这样一看就比较清楚了,init_type_list作为全局的list数组,所有通过type_init注册的对象就会被放连接在init_type_list[MODULE_INIT_QOM]这个list上。这个过程可以用如下图表示。

我们注意到module_init的定义

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

所以每一个type_init都会是一个函数do_qemu_init_xxxx,比如type_init(serial_register_types)将会被展开成

staic void __attribute__((constructor)) do_qemu_init_serial_register_types()
{
	register_module_init(serial_register_types, MODULE_INIT_QOM)
}

从constructor属性看,这将会使得该函数在main之前执行。

所以在qemu的main函数执行之前,图1中的各种链表已经准备好了。 在main函数中,我们可以看到,很快就调用了

module_call_init(MODULE_INIT_QOM);

看module_call_init定义,

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

    l = find_type(type);

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

可以看到该函数就是简单调用了注册在其上面的init函数,以serial举例:

static void serial_register_types(void)
{
    type_register_static(&serial_isa_info);
}

type_init(serial_register_types)

这里就是调用会调用serial_register_types,这个函数以serial_isa_info为参数调用了type_register_static。 函数调用链如下

type_register_static->type_register->type_register_internal->type_new

这一过程的目的就是利用TypeInfo构造出一个TypeImpl结构,之后插入到一个hash表之中,这个hash表以ti->name,也就是info->name为key,value就是生根据TypeInfo生成的TypeImpl。这样在,module_call_init(MODULE_INIT_QOM)调用之后,就有了一个type的哈希表,这里面保存了所有的类型信息。

二. Class的初始化

从第一部分我们已经知道,现在已经有了一个TypeImpl的哈希表。下一步就是初始化每个type了,这一步可以看成是class的初始化,可以理解成每一个type对应了一个class,接下来会初始化class。调用链

main->select_machine->find_default_machine->object_class_get_list->object_class_foreach

这里实在选择机器类型的时候顺便把各个type给初始化了。

void object_class_foreach(void (*fn)(ObjectClass *klass, void *opaque),
                          const char *implements_type, bool include_abstract,
                          void *opaque)
{
    OCFData data = { fn, implements_type, include_abstract, opaque };

    enumerating_types = true;
    g_hash_table_foreach(type_table_get(), object_class_foreach_tramp, &data);
    enumerating_types = false;
}

type_table_get就是之前创建的name为key,TypeImpl为value的哈希表。看看对这个表中的每一项调用的函数。

static void object_class_foreach_tramp(gpointer key, gpointer value,
                                       gpointer opaque)
{
    OCFData *data = opaque;
    TypeImpl *type = value;
    ObjectClass *k;

    type_initialize(type);
    k = type->class;

    if (!data->include_abstract && type->abstract) {
        return;
    }

    if (data->implements_type && 
        !object_class_dynamic_cast(k, data->implements_type)) {
        return;
    }

    data->fn(k, data->opaque);
}

我们来看 type_initialize函数

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->class = g_malloc0(ti->class_size);

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

        g_assert_cmpint(parent->class_size, <=, ti->class_size);
        memcpy(ti->class, parent->class, parent->class_size);
        ti->class->interfaces = NULL;
        ti->class->properties = g_hash_table_new_full(
            g_str_hash, g_str_equal, g_free, object_property_free);

        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);
            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);
        }
    } else {
        ti->class->properties = g_hash_table_new_full(
            g_str_hash, g_str_equal, g_free, object_property_free);
    }

    ti->class->type = ti;

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

开头我们可以看到,如果ti->class已经存在说明已经初始化了,直接返回,再看,如果有parent,会递归调用type_initialize,即调用父对象的初始化函数。

这里我们看到type也有一个层次关系,即QOM 对象的层次结构。在serial_isa_info 结构的定义中,我们可以看到有一个.parent域,

static const TypeInfo serial_isa_info = {
    .name          = TYPE_ISA_SERIAL,
    .parent        = TYPE_ISA_DEVICE,
    .instance_size = sizeof(ISASerialState),
    .class_init    = serial_isa_class_initfn,
};

这说明TYPE_ISA_SERIAL的父type是TYPE_ISA_DEVICE,在hw/isa/isa-bus.c中可以看到isa_device_type_info的父type是TYPE_DEVICE

static const TypeInfo isa_device_type_info = {
    .name = TYPE_ISA_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(ISADevice),
    .instance_init = isa_device_init,
    .abstract = true,
    .class_size = sizeof(ISADeviceClass),
    .class_init = isa_device_class_init,
};

依次往上溯我们可以得到这样一条type的链,

TYPE_ISA_SERIAL->TYPE_ISA_DEVICE->TYPE_DEVICE->TYPE_OBJECT

事实上,qemu中有两种根type,还有一种是TYPE_INTERFACE。

这样我们看到其实就跟各个type初始化的顺序没有关系了。不管哪个type最先初始化,最终都会初始化到object的type。对于object,只是简单的设置了一下分配了ti->class,设置了ti->class->type的值。如果type有interface,还需要初始化ti->class->interfaces的值,每一个interface也是一个type。如果父type有interfaces,还需要将父type的interface添加到ti->class->interfaces上去。

之后,最重要的就是调用parent->class_base_init以及ti->class_init了,这相当于C++里面的构造基类的数据。我们以一个class_init为例,

static void serial_isa_class_initfn(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = serial_isa_realizefn;
    dc->vmsd = &vmstate_isa_serial;
    dc->props = serial_isa_properties;
    set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}

我们可以看到这里从ObjectClass转换成了DeviceClass,然后做了一些簿记工作。这里为什么可以做转换呢。接下来看看Class的层次结构。

三. Class的层次结构

vmxnnet3的层次多一些,我们以他为例,首先看vmxnet3_info的定义。

static const TypeInfo vmxnet3_info = {
    .name          = TYPE_VMXNET3,
    .parent        = TYPE_PCI_DEVICE,
    .class_size    = sizeof(VMXNET3Class),
    .instance_size = sizeof(VMXNET3State),
    .class_init    = vmxnet3_class_init,
    .instance_init = vmxnet3_instance_init,
};

typedef struct VMXNET3Class {
    PCIDeviceClass parent_class;
    DeviceRealize parent_dc_realize;
} VMXNET3Class;

typedef struct PCIDeviceClass {
    DeviceClass parent_class;

    void (*realize)(PCIDevice *dev, Error **errp);
    int (*init)(PCIDevice *dev);/* TODO convert to realize() and remove */
    PCIUnregisterFunc *exit;
    PCIConfigReadFunc *config_read;
    PCIConfigWriteFunc *config_write;

	...
} PCIDeviceClass;


typedef struct DeviceClass {
    /*< private >*/
    ObjectClass parent_class;
    /*< public >*/
	...
} DeviceClass;


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

我们可以看到这样一种层次结构

VMXNET3Class->PCIDeviceClass->DeviceClass->ObjectClass

这可以看成C++中的继承关系,即当然基类就是ObjectClass,越往下包含的数据越具象。

从type_initialize中,我们可以看到,调用class_init(ti->class,ti->class_data) ,这里的ti->class就是刚刚分配出来的,对应到vmxnet3,这里就是一个VMXNET3Class结构, 注意到

memcpy(ti->class, parent->class, parent->class_size);

所以VMXNET3Class的各个父Class已经被初始化了。所以当进入vmxnet3_class_init之后,调用DEVICE_CLASS和PCI_DEVICE_CLASS以及VMXNET3_DEVICE_CLASS可以分别得到其基Class,类似于C++里面的派生类转换到基类。以

PCIDeviceClass *c = PCI_DEVICE_CLASS(class);

这句为例,我们知道这里class是vmxnet3对应的class,即class->type->name=”vmxnet3”。

#define PCI_DEVICE_CLASS(klass) \
     OBJECT_CLASS_CHECK(PCIDeviceClass, (klass), TYPE_PCI_DEVICE)

#define OBJECT_CLASS_CHECK(class_type, class, name) \
    ((class_type *)object_class_dynamic_cast_assert(OBJECT_CLASS(class), (name), \
                                               __FILE__, __LINE__, __func__))

ObjectClass *object_class_dynamic_cast_assert(ObjectClass *class,
                                              const char *typename,
                                              const char *file, int line,
                                              const char *func)
{
    ObjectClass *ret;

  	...
    ret = object_class_dynamic_cast(class, typename);
    ...
    return ret;
}


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 &&
           ...
    } else if (type_is_ancestor(type, target_type)) {
        ret = class;
    }

    return ret;
}

static bool type_is_ancestor(TypeImpl *type, TypeImpl *target_type)
{
    assert(target_type);

    /* Check if target_type is a direct ancestor of type */
    while (type) {
        if (type == target_type) {
            return true;
        }

        type = type_get_parent(type);
    }

    return false;
}

最终会进入object_class_dynamic_cast函数,在该函数中,根据class对应的type以及typename对应的type,判断是否能够转换,判断的主要依据就是type_is_ancestor, 这个判断target_type是否是type的一个祖先,如果是当然可以进行转换,否则就不行。

好了,总结一下,现在我们得到了什么,从最开始得TypeImpl初始化了每一个type对应的class,并且构建好了各个Class的继承关系。如下图所示,注意下面的***Class都包含了上面的一部分。

四. 对象的构造

我们上面已经看到了type哈希表的构造以及class的初始化,接下来讨论具体设备的创建。

以vmxnet3为例,我们需要再命令行指定-device vmxnet3。在main中,有这么一句话

if (qemu_opts_foreach(qemu_find_opts("device"),
                      device_init_func, NULL, NULL)) {
    exit(1);
}

对参数中的device调用device_init_func函数,调用链

device_init_func->qdev_device_add

在qdev_device_add中我们可以看到这么一句话

 dev = DEVICE(object_new(driver));

DeviceState *qdev_device_add(QemuOpts *opts, Error **errp)
{
    DeviceClass *dc;
    const char *driver, *path;
    DeviceState *dev;
    BusState *bus = NULL;
    Error *err = NULL;

    driver = qemu_opt_get(opts, "driver");
    if (!driver) {
        error_setg(errp, QERR_MISSING_PARAMETER, "driver");
        return NULL;
    }

    /* find driver */
    dc = qdev_get_device_class(&driver, errp);
    if (!dc) {
        return NULL;
    }

    /* find bus */
    path = qemu_opt_get(opts, "bus");
    if (path != NULL) {
        bus = qbus_find(path, errp);
        if (!bus) {
            return NULL;
        }
        if (!object_dynamic_cast(OBJECT(bus), dc->bus_type)) {
            error_setg(errp, "Device '%s' can't go on %s bus",
                       driver, object_get_typename(OBJECT(bus)));
            return NULL;
        }
    } else if (dc->bus_type != NULL) {
        bus = qbus_find_recursive(sysbus_get_default(), NULL, dc->bus_type);
        if (!bus || qbus_is_full(bus)) {
            error_setg(errp, "No '%s' bus found for device '%s'",
                       dc->bus_type, driver);
            return NULL;
        }
    }
    if (qdev_hotplug && bus && !qbus_is_hotpluggable(bus)) {
        error_setg(errp, QERR_BUS_NO_HOTPLUG, bus->name);
        return NULL;
    }

    /* create device */
    dev = DEVICE(object_new(driver));

    if (bus) {
        qdev_set_parent_bus(dev, bus);
    }

    qdev_set_id(dev, qemu_opts_id(opts));

    /* set properties */
    if (qemu_opt_foreach(opts, set_property, dev, &err)) {
        error_propagate(errp, err);
        object_unparent(OBJECT(dev));
        object_unref(OBJECT(dev));
        return NULL;
    }

    dev->opts = opts;
    object_property_set_bool(OBJECT(dev), true, "realized", &err);
    if (err != NULL) {
        error_propagate(errp, err);
        dev->opts = NULL;
        object_unparent(OBJECT(dev));
        object_unref(OBJECT(dev));
        return NULL;
    }
    return dev;
}

对象的调用是通过object_new(driver)实现的,这里的driver就是设备名,vmxnet3,

object_new->object_new_with_type->object_initialize_with_type


Object *object_new_with_type(Type type)
{
    Object *obj;

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

    obj = g_malloc(type->instance_size);
    object_initialize_with_type(obj, type->instance_size, type);
    obj->free = g_free;

    return obj;
}

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的父object,之后调用instance_init函数。这里又涉及到了object的继承。

typedef struct {
        PCIDevice parent_obj;
        ...
} VMXNET3State;

struct PCIDevice {
    DeviceState qdev;

   ...
};

struct DeviceState {
    /*< private >*/
    Object parent_obj;
    /*< public >*/

    
};

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

这次的继承体系是

VMXNET3State->PCIDevice->DeviceState->Object

这样就创建好了一个DeviceState,当然其实也是VMXNET3State,并且每一个父object的instance_init函数都已经调用好了,这里我们看看object、deviceobject、pcideviceobject的init函数都干了啥

static void object_instance_init(Object *obj)
{
    object_property_add_str(obj, "type", qdev_get_type, NULL, NULL);
}


static void device_initfn(Object *obj)
{
    DeviceState *dev = DEVICE(obj);
    ObjectClass *class;
    Property *prop;

    if (qdev_hotplug) {
        dev->hotplugged = 1;
        qdev_hot_added = true;
    }

    dev->instance_id_alias = -1;
    dev->realized = false;

    object_property_add_bool(obj, "realized",
                             device_get_realized, device_set_realized, NULL);
    object_property_add_bool(obj, "hotpluggable",
                             device_get_hotpluggable, NULL, NULL);
    object_property_add_bool(obj, "hotplugged",
                             device_get_hotplugged, device_set_hotplugged,
                             &error_abort);

    class = object_get_class(OBJECT(dev));
    do {
        for (prop = DEVICE_CLASS(class)->props; prop && prop->name; prop++) {
            qdev_property_add_legacy(dev, prop, &error_abort);
            qdev_property_add_static(dev, prop, &error_abort);
        }
        class = object_class_get_parent(class);
    } while (class != object_class_by_name(TYPE_DEVICE));

    object_property_add_link(OBJECT(dev), "parent_bus", TYPE_BUS,
                             (Object **)&dev->parent_bus, NULL, 0,
                             &error_abort);
    QLIST_INIT(&dev->gpios);
}

可以看到主要就是给对象添加了一些属性,object的type属性啊,device里面的realized、hotpluggable属性等,值得注意的是device_initfn还根据class->props添加的添加了属性, 在vmxnet3_class_init函数中,我们可以看到,在class被初始化的时候,其已经赋值vmxnet3_properties,

static Property vmxnet3_properties[] = {
    DEFINE_NIC_PROPERTIES(VMXNET3State, conf),
    DEFINE_PROP_BIT("x-old-msi-offsets", VMXNET3State, compat_flags,
                    VMXNET3_COMPAT_FLAG_OLD_MSI_OFFSETS_BIT, false),
    DEFINE_PROP_BIT("x-disable-pcie", VMXNET3State, compat_flags,
                    VMXNET3_COMPAT_FLAG_DISABLE_PCIE_BIT, false),
    DEFINE_PROP_END_OF_LIST(),
};

这样,object_new之后,创建的object其实已经具有了很多属性了,这是从父object那里继承过来的。

接着看qdev_device_add函数,调用了object_property_set_bool

object_property_set_bool->object_property_set_qobject->object_property_set->property_set_bool->device_set_realized->vmxnet3_realize

最终,我们的vmxnet3_realize函数被调用了,这也就完成了object的构造,不同于type和class的构造,object当然是根据需要创建的,只有在命令行指定了设备或者是热插一个设备之后才会有object的创建。Class和object之间是通过Object的class域联系在一起的。如下图所示。

五. 总结

从上文可以看出,我把QOM的对象构造分成三部分,第一部分是type的构造,这是通过TypeInfo构造一个TypeImpl的哈希表,这是在main之前完成的,第二部分是class的构造,这是在main中进行的,这两部分都是全局的,也就是只要编译进去了的QOM对象都会调用,第三部分是object的构造,这是构造具体的对象实例,在命令行指定了对应的设备时,才会创建object。从上上面也可以看出,正如Paolo Bonzini所说的,qemu在object方面的多态是一种class based的,而属性方面,是动态构造的,每个实例可能都有不同的属性,这是一种prototype based的多态。

本文主要是对整个对象的产生做了介绍,没有对interface和property做过多介绍,maybe以后又机会再详细说吧。

后记

这篇文章很早很早以前就说写了,15年还在学校就应该写的,结果今年忙于挖洞,一直就拖啊拖的,一直到现在终于把这个坑填上,鄙视一下自己,自己已经准备了好多qemu内容,一直没有时间填坑,希望有时间都填上。

参考

  1. QEMU设备模拟
  2. QOM exegesis and apocalypse, Paolo Bonzini, KVM Forum 2014


blog comments powered by Disqus