qemu2的qom系統分析(二)規範路徑和屬性

我們在qemu2的qom系統分析(-)對象系統 一文中分析過qom系統如何通過TypeInfo註冊對象,和對象的創建,繼承,實現。

qom系統除了這些能力,還提供了引用計數能力,和屬性設置,規範路徑的能力。

規範路徑的能力其實就是設置對象的child類型子屬性,從而形成對象之間的一個樹狀結構。這樣從根節點向下遍歷,每個對象都有一個唯一的路徑,所以就可以通過路徑來尋找對象。

所以我們要想了解對象的規範路徑如何實現就需要先看下對象的屬性是如何實現的。

1 屬性系統

首先什麼是屬性?其實就是對象的成員變量,成員變量要有名字,還要有值。所以也可以理解成key,value的形式。
qom對象支持的屬性類型有:

  • str
  • bool
  • int
  • uint
  • link
  • qobject

在object.c裏面對應設置這些屬性的函數名稱爲object_property_set_* ,獲取屬性value的函數名稱爲object_property_get_, 其中就是類型的名稱。其中link比較不好理解,其實對於link類型,key就是字符串,value就是對象對應的規範名稱,要想通過規範名稱找到對應的對象還需要進行查找。所以這其實類似一種軟連接方式。
我們以bool類型舉例來看看qom系統如何進行屬性設置

void object_property_set_bool(Object *obj, bool value,
                              const char *name, Error **errp)
{
    QBool *qbool = qbool_from_bool(value);
    object_property_set_qobject(obj, QOBJECT(qbool), name, errp);

    QDECREF(qbool);
}

qbool_from_bool創建一個bool類型的對象,通過object_property_set_qobject將這個bool對象設置進obj的屬性裏。 這其實是做了一個整合,在其他屬性的設置中其實最後都將創建一個對應object類型的對象,最後將這個對象向上轉型泛化爲object類型傳入object_property_set_qobject來進行屬性設置。

void object_property_set_qobject(Object *obj, QObject *value,
                                 const char *name, Error **errp)
{
    Visitor *v;

    v = qobject_input_visitor_new(value);
    object_property_set(obj, v, name, errp);
    visit_free(v);
}

ObjectProperty *object_property_find(Object *obj, const char *name,
                                     Error **errp)
{
    ObjectProperty *prop;
    ObjectClass *klass = object_get_class(obj);

    prop = object_class_property_find(klass, name, NULL);
    if (prop) {
        return prop;
    }

    prop = g_hash_table_lookup(obj->properties, name);
    if (prop) {
        return prop;
    }

    error_setg(errp, "Property '.%s' not found", name);
    return NULL;
}

函數並不是直接將qboject作爲對象屬性設置給對象,而是用訪問者模式中的訪問者包果了一下該對象。這裏object_property_set 函數的參數爲訪問者對象,訪問者對象裏面又持有屬性對象。所以屬性對象丟不了。我們繼續看object_property_set函數如何甚至屬性。

void object_property_set(Object *obj, Visitor *v, const char *name,
                         Error **errp)
{
    ObjectProperty *prop = object_property_find(obj, name, errp);
    if (prop == NULL) {
        return;
    }

    if (!prop->set) {
        error_setg(errp, QERR_PERMISSION_DENIED);
    } else {
        prop->set(obj, v, name, prop->opaque, errp);
    }
}

object_property_find函數比較負責,首先調用object_class_property_find函數看下對象所屬的類和成員是否有這個成員屬性,如果有則可以設置,否則就直接返回了, 另外設置屬性必須提供set方法(可以理解爲必須提供類seter方法才能設置),如果沒有提供則表明異常,打印錯誤。

在分析object_property_find前我們先看下怎麼給類和對象添加屬性。qom系統提供了兩個函數。object_class_property_add_和object_property_add_ 分別用於給類和對象添加屬性。*來表屬性的類型,這裏同樣以bool類型爲例進行分析。

void object_class_property_add_bool(ObjectClass *klass, const char *name,
                                    bool (*get)(Object *, Error **),
                                    void (*set)(Object *, bool, Error **),
                                    Error **errp)
{
    Error *local_err = NULL;
    BoolProperty *prop = g_malloc0(sizeof(*prop));

    prop->get = get;
    prop->set = set;

    object_class_property_add(klass, name, "bool",
                              get ? property_get_bool : NULL,
                              set ? property_set_bool : NULL,
                              property_release_bool,
                              prop, &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        g_free(prop);
    }
}

ObjectProperty *
object_class_property_add(ObjectClass *klass,
                          const char *name,
                          const char *type,
                          ObjectPropertyAccessor *get,
                          ObjectPropertyAccessor *set,
                          ObjectPropertyRelease *release,
                          void *opaque,
                          Error **errp)
{
    ObjectProperty *prop;

    if (object_class_property_find(klass, name, NULL) != NULL) {
        error_setg(errp, "attempt to add duplicate property '%s'"
                   " to object (type '%s')", name,
                   object_class_get_name(klass));
        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(klass->properties, g_strdup(name), prop);

    return prop;
}

其實很簡單,就是創建一個xxxProperty對象,然後插入到ObjectClass 的properties hash表中。 另外還可以設置屬性的set和get方法。

Object的屬性設置入法炮製,就不再分析了。讀者如果自行分析可以看到可以添加name以[*]結尾的字符串,其實就是把
[*]替換爲一個與現有屬性名稱不重複的數字進行添加。

下面來看object_property_find的實現

ObjectProperty *object_property_find(Object *obj, const char *name,
                                     Error **errp)
{
    ObjectProperty *prop;
    ObjectClass *klass = object_get_class(obj);

    prop = object_class_property_find(klass, name, NULL);
    if (prop) {
        return prop;
    }

    prop = g_hash_table_lookup(obj->properties, name);
    if (prop) {
        return prop;
    }

    error_setg(errp, "Property '.%s' not found", name);
    return NULL;
}
ObjectProperty *object_class_property_find(ObjectClass *klass, const char *name,
                                           Error **errp)
{
    ObjectProperty *prop;
    ObjectClass *parent_klass;

    parent_klass = object_class_get_parent(klass);
    if (parent_klass) {
        prop = object_class_property_find(parent_klass, name, NULL);
        if (prop) {
            return prop;
        }
    }

    prop = g_hash_table_lookup(klass->properties, name);
    if (!prop) {
        error_setg(errp, "Property '.%s' not found", name);
    }
    return prop;
}

object_property_find的實現首先先調用object_class_property_find函數,在ObjectClass的properties表中查找是否有同名屬性,如果有則直接返回,這也就說明ObjectClass和Object不允許有重名的函數,這也很好理解,其實就是靜態成員函數和非靜態成員函數不能重名。object_class_property_find 函數則要一直順着父類向上查找。

對於object_property_get_*方法也比較簡單,讀者自行分析。

到這裏屬性系統我們分析的就七七八八了。

2 規範路徑

理解了屬性系統規範路徑就不是問題了。
我們先來看一個Object的特殊類型的Proptery

void object_property_add_child(Object *obj, const char *name,
                               Object *child, Error **errp)
{
    Error *local_err = NULL;
    gchar *type;
    ObjectProperty *op;

    if (child->parent != NULL) {
        error_setg(errp, "child object is already parented");
        return;
    }

    type = g_strdup_printf("child<%s>", object_get_typename(OBJECT(child)));

    op = object_property_add(obj, name, type, object_get_child_property, NULL,
                             object_finalize_child_property, child, &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        goto out;
    }

    op->resolve = object_resolve_child_property;
    object_ref(child);
    child->parent = obj;

out:
    g_free(type);
}

這裏爲Object添加一個屬性,屬性的類型爲type = g_strdup_printf(“child<%s>”, object_get_typename(OBJECT(child)));
也就是child<child_typename>

另外設置了child->parent = object. 但是並沒有object_property_set_child函數,也就是沒有辦法給這個屬性賦值,也就是說孩子知道父親是誰,父親只知道孩子的名字,卻不知道孩子是誰,是不是很搞笑,其實不用但是,父親可以通過孩子的名字找到它,這就是規範路徑的作用。 通向我們也可以根據孩子知道它的名字(規範路徑)

先來看根如何獲取對象的規範路徑

gchar *object_get_canonical_path(Object *obj)
{
    Object *root = object_get_root();
    char *newpath, *path = NULL;

    while (obj != root) {
        char *component = object_get_canonical_path_component(obj);

        if (path) {
            newpath = g_strdup_printf("%s/%s", component, path);
            g_free(component);
            g_free(path);
            path = newpath;
        } else {
            path = component;
        }

        obj = obj->parent;
    }

    newpath = g_strdup_printf("/%s", path ? path : "");
    g_free(path);

    return newpath;
}

gchar *object_get_canonical_path_component(Object *obj)
{
    ObjectProperty *prop = NULL;
    GHashTableIter iter;

    g_assert(obj);
    g_assert(obj->parent != NULL);

    g_hash_table_iter_init(&iter, obj->parent->properties);
    while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&prop)) {
        if (!object_property_is_child(prop)) {
            continue;
        }

        if (prop->opaque == obj) {
            return g_strdup(prop->name);
        }
    }

    /* obj had a parent but was not a child, should never happen */
    g_assert_not_reached();
    return NULL;
}
static inline bool object_property_is_child(ObjectProperty *prop)
{
    return strstart(prop->type, "child<", NULL);
}

object_property_is_child 判斷一個屬性是否爲一個孩子屬性。如果是的話,則持有該屬性的對象就是路徑的一部分。object_get_canonical_path 就是順着最底層對象的parent向上遍歷,找到每一級路徑,直到找到根對象,最後來組裝成對象的規範路徑。

另外根據規範路徑名稱來找到對象的函數爲object_resolve_path。

Object *object_resolve_path(const char *path, bool *ambiguous)
{
    return object_resolve_path_type(path, TYPE_OBJECT, ambiguous);
}

Object *object_resolve_path_type(const char *path, const char *typename,
                                 bool *ambiguousp)
{
    Object *obj;
    gchar **parts;

    parts = g_strsplit(path, "/", 0);
    assert(parts);

    if (parts[0] == NULL || strcmp(parts[0], "") != 0) {
        bool ambiguous = false;
        obj = object_resolve_partial_path(object_get_root(), parts,
                                          typename, &ambiguous);
        if (ambiguousp) {
            *ambiguousp = ambiguous;
        }
    } else {
        obj = object_resolve_abs_path(object_get_root(), parts, typename, 1);
    }

    g_strfreev(parts);

    return obj;
}

函數其實比較簡單,就是從object_get_root()向下搜索路徑,直到找到對應的obj爲止,另外路徑爲相對路徑的情況下,可能有個子路徑重名,比如在/a/b/c 和/d/b/c中查找相對路徑b/c 則就會出現重名的路徑,通過ambiguousp參數作爲傳出值,來告訴調用者是否有衝突。

總結

最後總結一下

1 給對象添加屬性使用函數object_property_add_*

可以使用name[*] 類型的字符串添加屬性,系統會默認將* 替換爲一個數字id,作爲名稱添加到屬性裏表(屬性三要素 類型,名稱,和值)。 類型如"int", “bool”,"str"等,對於子類型爲child<$childname>.

另外名稱不能和類屬性重名也不能和其他屬性重名

另外參數爲 setter和geeter函數

2 給類添加屬性(相當於靜態變量)object_class_property_add_*
同1

3 設置屬性值。 object_property_set_*
必須先添加了屬性才能設置屬性的值。

4 獲取屬性的值 object_property_get_bool_*

5 規範路徑

  1. 根據規範路徑查找對象object_resolve_path
  2. 根據對象獲取規範路徑object_get_canonical_path
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章