1. 程式人生 > >qemu如何實現面向物件模型QOM(程式碼講解)

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