qemu如何实现面向对象模型QOM(代码讲解)

(说明:本文档只是对QOM中关键实现片段进行叙述,更加详细的代码,请查看本文涉及的代码文件)
有两个问题需要解答:

  1. QOM中如何将所有的类储存起来的,并且完整地呈现给使用者的。
  2. 面向对象编程,有三个重要的特性——封装、继承和多态。封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了代码重用。而多态则是为了实现另一个目的——接口重用。多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。本文档将对QOM中实现的封装、继承和多态三个特性分别展开叙述。

    • 封装
      封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。被封装的对象通常被称为抽象数据类型。
    • 继承
      继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
      通过继承创建的新类称为“子类”或“派生类”。被继承的类称为“基类”、“父类”或“超类”。
      继承的过程,就是从一般到特殊的过程。
    • 多态
      多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。

整体把握几个数据结构

下面这张图可以简要说明ObjectClass、Object、TypeImpl、InterfaceClass之间的关系。通过这些数据结构中的指针,我们可以很方便地找到其他对应的数据结构。
这里写图片描述

QOM如何将所有的类储存起来的

我们将会看到,QOM中维持了一个静态变量的hash表,这张hash表中保存了所有注册过的类的TypeImpl数据结构。

用户如何通过QOM提供的数据结构表示一个类?

在我们之前对QOM的介绍篇中已经说明了,在qemu中如何创建一个新的类。用户在创建类时要实现两个数据结构:描述类的数据结构(第一个属性必须是父类数据结构的实例)、描述类产生的实例的数据结构(第一个属性必须是父类对象的数据结构的实例),并且需要创建一个数据结构TypeInfo的实例,用来描述这种类的类型。

通过创建TypeInfo,可以告诉qemu哪些信息呢?
我们再次查看一下TypeInfo数据结构,这个数据结构中包含了这个类型的父类的名字,insatnce的size信息、class的size信息,instance的初始化和垃圾回收函数、class的初始化和垃圾回收函数。有了这些信息我们就可以:
* 为instance、class初始化第一个属性(instance的第一个属性必须是父类对象的数据结构的实例,class的一个属性必须是父类的数据结构的实例)
* 为instance、class分配合适的内存空间,这样我们就可以将一个Object的指针动态cast为我们需要的对象类型。
* 类的数据结构一般包含大量的函数指针,用于对对象进行操作。class的init函数可以将这些函数指针初始化。然后所有含有这个类数据结构指针的对象,都可以调用这些函数。
* 对象的数据结构一般包含了大量变量,是对象的一些属性。instance的init函数可以把这些属性初始化,相当于C++中的构造函数。
* 一个类可以实现多个接口,这些接口也是一个类,并且是抽象类,含有虚拟函数指针。

可以说,有了这个数据结构,就有了类的最基本的信息。(代码在include/qom/object.h中)

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中的?

QOM提供了type_register和type_register_static方法,用户可以调用这两个方法注册一个Type,需要传进去的参数就是TypeInfo的指针。(代码在qom/object.c中)

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

可以看到它们都调用了type_register_internal(TypeInfo*)函数。(代码在qom/object.c中)

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

    type_table_add(ti);
    return ti;
}

type_new(TypeInfo*)函数将TypeInfo数据结构中的相关信息,赋值给TypeImpl数据结构中的属性。而type_table_add(TypeImpl *)函数将调用type_new()获得的TypeImpl指针保存到静态的hash表中。
我们回顾一下TypeImpl的数据结构:(代码在qom/object.c中)

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_finalize)(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];
};

而type_new()函数就是针对TypeInfo中的各种信息,给TypeImpl赋值:(代码在qom/object.c中)

static TypeImpl *type_new(const TypeInfo *info)
{
    TypeImpl *ti = g_malloc0(sizeof(*ti));
    int i;

    g_assert(info->name != NULL);

    if (type_table_lookup(info->name) != NULL) {
        fprintf(stderr, "Registering `%s' which already exists\n", info->name);
        abort();
    }

    ti->name = g_strdup(info->name);
    ti->parent = g_strdup(info->parent);

    ti->class_size = info->class_size;
    ti->instance_size = info->instance_size;

    ti->class_init = info->class_init;
    ti->class_base_init = info->class_base_init;
    ti->class_finalize = info->class_finalize;
    ti->class_data = info->class_data;

    ti->instance_init = info->instance_init;
    ti->instance_post_init = info->instance_post_init;
    ti->instance_finalize = info->instance_finalize;

    ti->abstract = info->abstract;

    for (i = 0; info->interfaces && info->interfaces[i].type; i++) {
        ti->interfaces[i].typename = g_strdup(info->interfaces[i].type);
    }
    ti->num_interfaces = i;

    return ti;
}

最后,通过type_table_add()保存到静态的hash表中(代码在qom/object.c中)

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;  //静态的类型的hash表,保存了全部被注册的类型

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

    return type_table;
}

面向对象特性的实现

封装的实现

在考察QOM如何实现封装时,我们需要再次审视Object这个数据结构包含了哪些属性:(代码在/include/qom/object.h中)

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

值得注意的是,这个Object的数据结构中的一行注释:“private”,它表示Object中的所有属性都是私有的,只能被类的内部函数访问、修改。这个数据结构中体现封装特性的私有变量是properties,它是一张hash表,这个变量包含了Object中的所有可访问和修改的数据、函数。这个Hash表中,每一个entry有对应的key和value,key是这个property的name,而value是一个ObjectProperty数据结构的指针,ObjectProperty的实现代码如下:(代码在include/qom/object.h中)

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

可以看到,ObjectProperty包含了这个属性的名字、类型、描述、get和set的方法,解析(resolve)和释放(release)的方法,以及这个property特有的属性,用void *类型的指针来表示。
QOM使用这样一个数据结构,将对象的每个数据都保存在这样一个单元之中,从而让实现了封装。
当用户需要向Object中增加属性时,就可以直接调用object_property_add函数,这个函数向object的properties的hash表中插入了一个property。(代码在qom/object.c中)

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

继承的实现

继承包括:实现继承、接口继承和可视继承三种。可视继承与图形界面相关,我们不考虑。 实现继承是指使用基类的属性和方法而无需额外编码的能力; 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力。
* 实现继承
我们创建一个新类时,需要实现两个数据结构:类的数据结构和对象的数据结构,由于类的数据结构中第一个属性就是父类的数据结构的实例,而对象的数据结构中,第一个属性就是父类对应的对象的实例。这样的实现方式,使得QOM天然地支持显现继承。
* 接口继承
接口在QOM中也有一个对应的类的数据结构:(代码在include/qom/object.h中)

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

在QOM中一个类可以实现多个接口,也就是实现接口继承。在ObjectClass中与接口继承相关的属性就是interfaces,它在ObjectClass是一条链表,链表中的每个entry都是一个InterfaceClass的指针,最终对应到interface的TypeImpl数据结构的指针。我们可以通过给TypeImpl指针对应的类数据结构中的函数指针赋值,达到实现接口的目的。(代码在include/qom/include.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;
};

多态的实现

多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。为了实现多态,QOM实现了一个非常重要的功能,就是动态cast。我们可以使用相关的函数,将一个Object的指针在运行时cast为子类对象的指针,可以将一个ObjectClass的指针在运行时cast为子类的指针。这样就可以调用子类中定义的函数指针来完成相应的功能。

动态cast检查的主要实现函数是:(代码在qom/object.c中)

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

    if (!class) {
        return NULL;
    }

    type = class->type;   //classtype指针总是指向这个类的TypeImpl,因此从这里获得这个类的类型。
    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;
    }
    //检查是否需要动态cast为接口类对象,然后检查每个接口
    //主要检查目标类型是不是当前所指向的类型的祖先。
    if (type->class->interfaces &&
            type_is_ancestor(target_type, 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;
}

垃圾回收

在Object的数据结构中有一个变量:ref,这个变量用于对Object引用的计数,如果ref的值变为0,就意味着系统不会继续使用这个对象了,那么就可以对这个变量的内存空间等进行回收操作。
* 在TypeInfo中,我们可以定义instance_finalize,对于引用计数为0的Object进行垃圾回收操作。
* Object数据结构中有一个ObjectFree *类型的函数指针free,当Object的引用计数为0时,就会调用这个函数进行垃圾回收。
* QOM自己实现了默认的垃圾回收操作:(代码在qom/object.c中)

//减少obj的引用计数,如果引用计数为0,将进行垃圾回收
void object_unref(Object *obj)
{
    if (!obj) {
        return;
    }
    g_assert_cmpint(obj->ref, >, 0);

    /* parent always holds a reference to its children */
    if (atomic_fetch_dec(&obj->ref) == 1) {
        object_finalize(obj);
    }
}
//obj的默认的析构函数
static void object_finalize(void *data)
{
    Object *obj = data;
    TypeImpl *ti = obj->class->type;

    object_property_del_all(obj); //删除obj中的所有属性
    object_deinit(obj, ti);   //调用TypeImpl中finalize函数进行析构(请看后面)

    g_assert_cmpint(obj->ref, ==, 0);   //确定引用计数为0
    if (obj->free) {
        obj->free(obj);   //如果obj有free函数指针,那么就会调用该函数
    }
}
// 调用TypeImpl中的实例析构函数。如果存在父类,需要继续调用父类的实例析构函数
// 调用父类实例析构函数是因为一个对象数据结构中,第一个属性就是父类对象的实例
// 当我们需要对对象析构时,不仅要调用当前类的析构方法,也需要调用父类的析构方法
// 将对象中的第一个属性进行析构。
static void object_deinit(Object *obj, TypeImpl *type)
{
    if (type->instance_finalize) {
        type->instance_finalize(obj);
    }

    if (type_has_parent(type)) {
        object_deinit(obj, type_get_parent(type));
    }
}
  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
QEMU是一款开源的虚拟机软件,它支持模拟多种不同的硬件设备,包括虚拟显示器。在QEMU中,虚拟显示器驱动程序主要由以下两个部分组成: 1. 显示器后端(Display Backends):负责将客户机的图形数据渲染成显存中的像素数据,并将其发送给前端。 2. 显示器前端(Display Frontends):负责将后端发送的像素数据显示出来。 下面是一个简单的QEMU虚拟显示器驱动程序实现: ```c #include "qemu/osdep.h" #include "qemu-common.h" #include "qemu/module.h" #include "ui/console.h" #include "ui/console-input.h" #include "ui/pixel_ops.h" #include "hw/display/virtio-gpu.h" #define TYPE_QEMU_DISPLAY "qemu-display" #define QEMU_DISPLAY(obj) \ OBJECT_CHECK(QemuDisplayState, (obj), TYPE_QEMU_DISPLAY) typedef struct QemuDisplayState { VirtIOGPU *virtio_gpu; DisplaySurface *surface; Console *console; uint8_t *fb; int fb_stride; } QemuDisplayState; static void qemu_display_refresh(DisplayChangeListener *dcl, const QRegion *region) { QemuDisplayState *s = QEMU_DISPLAY(dcl); DisplaySurface *surface = s->surface; int pos_x, pos_y; uint8_t *fb = s->fb; int fb_stride = s->fb_stride; int width = surface->width; int height = surface->height; for (pos_y = 0; pos_y < height; pos_y++) { for (pos_x = 0; pos_x < width; pos_x++) { uint8_t *pixel = fb + pos_y * fb_stride + pos_x * 4; uint32_t color = pixel_get(surface, pixel); console_write_graphic(s->console, pos_x, pos_y, color); } } } static void qemu_display_destroy(DisplayChangeListener *dcl) { QemuDisplayState *s = QEMU_DISPLAY(dcl); qemu_free(s->fb); s->fb = NULL; display_surface_unref(s->surface); s->surface = NULL; virtio_gpu_cleanup(s->virtio_gpu); s->virtio_gpu = NULL; } static void qemu_display_init(DisplayChangeListener *dcl, DisplaySurface *surface, int x, int y, int w, int h, int stride) { QemuDisplayState *s = QEMU_DISPLAY(dcl); s->surface = surface; s->console = graphic_console_init(surface->width, surface->height, qemu_get_display_type()); s->fb_stride = surface->width * 4; s->fb = qemu_mallocz(s->fb_stride * surface->height); s->virtio_gpu = virtio_gpu_init(s->fb, s->fb_stride, surface->width, surface->height); } static DisplayChangeListener *qemu_display_create(VirtIOGPU *virtio_gpu) { QemuDisplayState *s = qemu_mallocz(sizeof(QemuDisplayState)); DisplayChangeListener *dcl = display_state_create(s, qemu_display_refresh, qemu_display_destroy, qemu_display_init); s->virtio_gpu = virtio_gpu; return dcl; } static void qemu_display_initfn(Object *obj) { DeviceState *dev = DEVICE(obj); VirtIOGPU *virtio_gpu = VIRTIO_GPU(dev); DisplayChangeListener *dcl = qemu_display_create(virtio_gpu); display_state_set_surface(dcl, virtio_gpu_get_primary(virtio_gpu)); virtio_gpu_set_displaychange_listener(virtio_gpu, dcl); } static void qemu_display_class_init(ObjectClass *oc, void *data) { DeviceClass *dc = DEVICE_CLASS(oc); VirtIOGPUClass *vgc = VIRTIO_GPU_CLASS(oc); dc->reset = virtio_device_reset; dc->vmsd = &vmstate_virtio_pci_device; vgc->init_display = qemu_display_initfn; } static const TypeInfo qemu_display_info = { .name = TYPE_QEMU_DISPLAY, .parent = TYPE_VIRTIO_GPU, .instance_size = sizeof(QemuDisplayState), .class_init = qemu_display_class_init, }; static void qemu_display_register_types(void) { type_register_static(&qemu_display_info); } type_init(qemu_display_register_types) ``` 这个驱动程序实现了一个简单的QEMU虚拟显示器,它使用VirtIO-GPU协议与客户机进行通信。在初始化函数qemu_display_initfn中,驱动程序会创建一个DisplayChangeListener对象,并将其注册到virtio_gpu中。当客户机发送图形数据时,virtio_gpu会将数据传递给驱动程序的后端,并在显示内容更新时通知前端刷新显示。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值