(轉載)本文發自 http://www.binss.me/blog/qemu-note-of-qemu-object-model/,轉載請註明出處。
QOM(Qemu Object Model)是QEMU最新的設備模型。QEMU一開始採用ad hoc,每種設備都有不同的表示方式,非常混亂。於是開發了qdev,將所有的模擬設備進行了整合,變成了一種單根結點(系統總線)的樹狀形式,並且增加了hotplug的功能。後來可能由於Device和Bus之間的複雜關係,又開發了QOM。
QOM是QEMU在C的基礎上自己實現的一套面向對象機制,負責將device、bus等設備都抽象成爲對象。對象的初始化分爲四步:
- 將 TypeInfo 註冊 TypeImpl
- 實例化 ObjectClass
- 實例化 Object
- 添加 Property
根據QEMU的 wiki ,QOM沒有構造和析構的概念。但矛盾的是根據代碼, TypeInfo 中定義的 class_init 和 instance_init 無論從名字還是實現上都做了對象的初始化工作,比如設置對象成員的值。但爲什麼說它們最多隻能算是初始化函數呢?
Everything in QOM is a device
根據實現,經過 class_init 和 instance_init 產生設備對應Object後,這個Object是不能直接使用的。其真正初始化邏輯的大頭都放在 realize 中做,比如創建對應的memory region,掛載到對應bus上等等。只有在 realize 後,設備纔算真正構造完成,可以拿來用了。因此QEMU認爲,類似構造和析構的是realize和unrealize。而在設備的生命週期中,可以被realize和unrealize多次。這也是最讓我莫名其妙的地方。
爲了保持習慣,本文會依然將 class_init 和 instance_init 當做構造函數,稱前者爲類構造函數,後者爲類實例構造函數。
TypeInfo => ModuleEntry
TypeInfo 定義了一種類型。如 KVM在kvm-all.c
中的定義:
static const TypeInfo kvm_accel_type = { | |
.name = TYPE_KVM_ACCEL, | |
.parent = TYPE_ACCEL, | |
.class_init = kvm_accel_class_init, | |
.instance_size = sizeof(KVMState), | |
}; |
包含 類型的名稱(name)、父類名稱(parent)、Object實例的大小(instance_size)、是否抽象類(abstract)、初始化函數(class_init)。
代碼底部有 type_init ,由 C run-time(CRT)負責執行:
type_init(kvm_type_init) => module_init(function, MODULE_INIT_QOM) => register_module_init(function, 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); | |
} |
創建了 type 爲 MODULE_INIT_QOM ,init爲 kvm_type_init 的 ModuleEntry ,並加入到 MODULE_INIT_QOM 的 ModuleTypeList 中。
ModuleEntry => TypeImpl
在 main.c(vl.c) 的一開始執行了 module_call_init(MODULE_INIT_QOM) ,它從 init_type_list 中取出對應的 ModuleTypeList ,然後對裏面的 ModuleEntry 成員都調用 init 函數。
對於上文提到的 ModuleEntry ,調用的是 kvm_type_init => type_register_static(&kvm_accel_type) => type_register => type_register_internal
static TypeImpl *type_register_internal(const TypeInfo *info) | |
{ | |
TypeImpl *ti; | |
ti = type_new(info); | |
type_table_add(ti); | |
return ti; | |
} |
它根據 kvm_accel_type(TypeInfo) 創建一個名爲TYPE_KVM_ACCEL的 TypeImpl 類型的結構。
同時將該 TypeImpl 註冊到全局 type_table 中,key爲類型名稱,即 TYPE_KVM_ACCEL
ObjectClass
struct ObjectClass | |
{ | |
/*< private >*/ | |
Type type; // 用typedef定義的 TypeImpl 指針 | |
GSList *interfaces; | |
const char *object_cast_cache[OBJECT_CLASS_CAST_CACHE]; | |
const char *class_cast_cache[OBJECT_CLASS_CAST_CACHE]; | |
ObjectUnparent *unparent; | |
GHashTable *properties; | |
}; |
ObjectClass 屬於類對象,它是所有類對象的基類。
TypeImpl => ObjectClass
有兩種路徑,一種是主動地調用: object_class_get_list => object_class_foreach => g_hash_table_foreach(object_class_foreach_tramp) => object_class_foreach_tramp => type_initialize
比如 object_class_get_list(TYPE_DEVICE, false) 創建 TYPE_DEVICE 類型的 ObjectClass
該過程用到glic的函數 g_hash_table_foreach ,見 https://developer.gnome.org/glib/stable/glib-Hash-Tables.html#g-hash-table-foreach
另一種是被動調用,如:
- object_class_by_name
- object_class_get_parent
- object_new_with_type
- object_initialize_with_type
在獲取 class、class的parent、創建type的object、初始化TypeImpl的object時,調用 type_initialize
type_initialize | |
=> 如果 TypeImpl 已創建(class成員有值),返回 | |
=> ti->class = g_malloc0(ti->class_size) 根據class_size分配內存空間 | |
=> type_get_parent(ti) 獲取父類的TypeImpl | |
=> memcpy(ti->class, parent->class, parent->class_size) 將parent的class拷貝到自己class的最前面 | |
=> ti->class->properties = g_hash_table_new_full 創建存放property的hash table | |
=> type_initialize_interface 初始化class的接口,包括父類和自己的 | |
=> ti->class->type = ti 設置class的type爲對應TypeImpl | |
=> parent->class_base_init 如果parent定義了 class_base_init ,調用之 | |
=> ti->class_init(ti->class, ti->class_data) 調用class的 class_init |
對於 kvm_accel_type 這個 TypeInfo 的 TypeImpl ,調用的class_init是 kvm_accel_class_init ,它將傳入的 ObjectClass 強轉爲子類 AccelClass ,設置 init_machine 成員爲 kvm_init
這裏的class是該類型的類實例,它的基類是 ObjectClass 。
繼承
從創建流程可以看出,在創建類對象時,會調用 type_initialize ,其會遞歸地對 TypeImpl 中的 parent 成員(TypeImpl)遞歸調用 type_initialize ,然後將創建出來的相應 ObjectClass 拷貝到自己class的最前面。
類對象的第一個成員是 parent_class ,由於父類對象會拷到子類對象的最前面,因此可以認爲其指向父類的對象,如此構成鏈狀的繼承鏈,最終指向基類對象 ObjectClass
比如 kvm_accel_type 對應的類對象,該類對象作爲葉子類型並沒有定義,但其父類 AccelClass 在代碼中有定義,其的第一個成員爲 ObjectClass ,表示其繼承自 ObjectClass 。爲了能表示該葉子類型繼承 AccelClass ,它修改了 AccelClass的一些對象成員,這樣在某種程度上表示了繼承關係。比如修改了函數指針成員的指向,相當於實現了虛函數。
又如: register_info 對應的類對象 => PCIDeviceClass => DeviceClass => ObjectClass 構成繼承鏈,最前端的葉子類型通過修改 PCIDeviceClass 成員進行定義。
強制類型轉換
將一個父類的指針轉換爲子類的指針是不安全的,爲了實現這種轉換,各類需要提供強制類型轉換的宏,如:
#define ACCEL_CLASS(klass) \ | |
OBJECT_CLASS_CHECK(AccelClass, (klass), TYPE_ACCEL) | |
#define OBJECT_CLASS_CHECK(class_type, class, name) \ | |
((class_type *)object_class_dynamic_cast_assert(OBJECT_CLASS(class), (name), \ | |
__FILE__, __LINE__, __func__)) |
如果類對象指針的name和目標子類的name一致,或類對象指針是目標子類的祖先,則執行轉換,否則 abort
反過來,從子類指針轉換爲父類指針是安全的,因爲類的第一項就指向父類,訪問時不會存在越界等問題。
Object
Object 屬於類實例對象,它是所有類實例對象的基類。
struct Object | |
{ | |
/*< private >*/ | |
ObjectClass *class; // 指向類對象 | |
ObjectFree *free; | |
GHashTable *properties; // 維護屬性的哈希表 | |
uint32_t ref; // 引用計數 | |
Object *parent; // 指向父類實例對象,實現繼承 | |
}; |
可以看到其第一個成員指向類對象,同時維護有區別於類屬性的類實例屬性。
創建流程
就流程而言,在C runtime 根據 TypeInfo 創建了 TypeImpl 後,此後主要根據 TypeImpl 創建 ObjectClass 和 Object
以 TypeInfo(kvm_accel_type) 爲例,其創建的 TypeImpl 在以下流程發揮作用:
main => configure_accelerator => accel_init_machine(acc, ms) | |
=> ObjectClass *oc = OBJECT_CLASS(acc) 將AccelClass指針轉換成父類(ObjectClass)指針 | |
=> object_class_get_name 獲取 ObjectClass->TypeImpl 的類名,如 kvm-accel | |
=> ACCEL(object_new(cname)) 利用名稱創建 AccelState 對象 | |
=> acc->init_machine(ms) 初始化machine,實際上是調用 kvm_init | |
object_new | |
=> type_get_by_name(typename) 根據類名查type_table獲取 TypeImpl | |
=> object_new_with_type => type_initialize 創建 TypeImpl 對應的類對象,設置到對應 TypeImpl->class 中 | |
=> g_malloc(type->instance_size) 分配類實例對象的內存 | |
=> object_initialize_with_type 創建類實例對象 | |
=> type_initialize 會再次嘗試實例化類對象 | |
=> obj->class = type->class 設置類實例對象的類對象爲 TypeImpl->class | |
=> obj->properties = g_hash_table_new_full 創建存放類實例對象property的hash table | |
=> object_init_with_type => object_init_with_type 如果 TypeImpl 有父類,遞歸調用object_init_with_type | |
=> ti->instance_init(obj) 如果定義了類實例的構造函數,調用之 |
繼承
定義上的繼承主要指類的繼承,既然類對象已經通過包含的方式實現了繼承,那麼類實例對象就可以通過調用自己的class成員調用父類的函數,訪問父類的class property。
但在QEMU實現的這套面向對象模型中,類實例對象也擁有自己的構造函數,因此根據繼承關係,需要對父類實例對象的構造函數進行調用。
從創建流程可以看出,在創建類實例對象時,會調用 object_init_with_type ,其會遞歸地對 TypeImpl 中的 parent 成員遞歸調用 object_init_with_type ,從而讓所有父類的 instance_init 都得到調用,在調用時傳入的是當前對象的地址,相當於在當前對象上對父類實例對象進行構造。
同理,類實例對象的第一個成員是 parent_obj ,指向父類的實例對象,如此構成鏈狀的繼承鏈,最終指向基類實例對象 Object
如: kvm_accel_type的類實例Object => AccelState => Object
又如: register_info的類實例Object => PCIDevice => DeviceState => Object
強制類型轉換
同理,將一個父類實例的指針轉換爲子類實例指針是不安全的。爲了實現這種轉換,各類需要提供強制類型轉換的宏,如:
#define ACCEL(obj) \ | |
OBJECT_CHECK(AccelState, (obj), TYPE_ACCEL) | |
#define OBJECT_CHECK(type, obj, name) \ | |
((type *)object_dynamic_cast_assert(OBJECT(obj), (name), \ | |
__FILE__, __LINE__, __func__)) |
如果類實例對象指針的name和目標子類實例的name一致,或類實例對象指針是目標子類的祖先,則執行轉換,否則 abort。
反過來,從子類實例指針轉換爲父類實例指針是安全的,因爲類實例的第一項就指向父類實例,訪問時不會存在越界等問題。
屬性
屬性分爲類對象(ObjectClass)屬性和類實例對象(Object)屬性,存儲於 properties 成員中。properties 是一個 GHashTable ,存儲了 屬性名 到 ObjectProperty 的映射。
屬性模版
用於創建屬性對象 ObjectProperty
struct Property { | |
const char *name; | |
PropertyInfo *info; | |
ptrdiff_t offset; | |
uint8_t bitnr; | |
QType qtype; | |
int64_t defval; | |
int arrayoffset; | |
PropertyInfo *arrayinfo; | |
int arrayfieldsize; | |
}; |
屬性對象
屬性對象包含屬性名稱、類型、描述,類型對應的屬性結構,以及相應訪問函數。
typedef struct ObjectProperty | |
{ | |
gchar *name; | |
gchar *type; | |
gchar *description; | |
ObjectPropertyAccessor *get; | |
ObjectPropertyAccessor *set; | |
ObjectPropertyResolve *resolve; | |
ObjectPropertyRelease *release; | |
void *opaque; | |
} ObjectProperty; |
如對於bool類型的屬性,opaque爲 BoolProperty ,set爲 property_set_bool ,get爲 property_get_bool 。
typedef struct BoolProperty | |
{ | |
bool (*get)(Object *, Error **); | |
void (*set)(Object *, bool, Error **); | |
} BoolProperty; |
用於保存用戶傳入的 getter 和 setter 。
getter / setter (callback hook)
定義了在設置/讀取屬性時觸發的函數。
比如 device 類型的 instance_init 即 device_initfn 中,定義了 realized 屬性:
object_property_add_bool(obj, "realized", device_get_realized, device_set_realized, NULL) |
則 getter 爲 device_get_realized , setter 爲 device_set_realized
靜態屬性
凡是在代碼中就已經定義好名稱和類型的屬性,都是靜態屬性。包括在初始化過程中添加 和 props 。
初始化過程中添加
比如對於 TypeInfo x86_cpu_type_info ,類實例初始化函數 x86_cpu_initfn 定義好了屬性:
object_property_add(obj, "family", "int", | |
x86_cpuid_version_get_family, | |
x86_cpuid_version_set_family, NULL, NULL, NULL); | |
object_property_add_alias(obj, "kvm_steal_time", obj, "kvm-steal-time", &error_abort); |
該屬性會直接加到類實例對象的properties中。
props
一些類對象會在 class_init 中設置 props 成員,比如 TypeInfo host_x86_cpu_type_info 在 host_x86_cpu_class_init 設置爲 host_x86_cpu_properties:
static Property host_x86_cpu_properties[] = { | |
DEFINE_PROP_BOOL("migratable", X86CPU, migratable, true), | |
DEFINE_PROP_BOOL("host-cache-info", X86CPU, cache_info_passthrough, false), | |
DEFINE_PROP_END_OF_LIST() | |
}; | |
#define DEFINE_PROP_BOOL(_name, _state, _field, _defval) { \ | |
.name = (_name), \ | |
.info = &(qdev_prop_bool), \ | |
.offset = offsetof(_state, _field) \ | |
+ type_check(bool, typeof_field(_state, _field)), \ | |
.qtype = QTYPE_QBOOL, \ | |
.defval = (bool)_defval, \ | |
} | |
// 閉包 | |
PropertyInfo qdev_prop_bool = { | |
.name = "bool", | |
.get = get_bool, | |
.set = set_bool, | |
}; |
而類實例 X86CPU 中定義了這些屬性:
struct X86CPU { | |
bool migratable; | |
... | |
bool cache_info_passthrough; | |
... | |
}; |
於是 X86CPU.migratable 和 X86CPU.cache_info_passthrough 兩個成員被定義成屬性。
在父類 device_type_info 的類實例初始化函數 device_initfn 中,對所有的props,有:
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)); |
而 qdev_property_add_static :
=> object_property_add(obj, prop->name, prop->info->name, prop->info->get, prop->info->set, prop->info->release, prop, &local_err) | |
根據Property中的數據,創建ObjectProperty,並將其加到類實例對象的 properties 中 | |
關鍵是將閉包中的get和set取出,作爲ObjectProperty的get和set | |
=> object_property_set_description 設置屬性的描述字符串 | |
=> 設置屬性的默認值 |
查看
可通過命令查看設備的靜態屬性,參數爲設備 TypeInfo 的 name:
/home/binss/work/qemu/qemu-2.8.1.1/x86_64-softmmu/qemu-system-x86_64 -device Broadwell-x86_64-cpu,? |
但是, x86_64-cpu 抽象設備無法打。 host-x86_64-cpu 無法列出。
動態屬性
指在運行時動態進行添加的屬性。比如用戶通過參數傳入了一個設備,需要作爲屬性和其它設備關聯起來。
典型的動態屬性就是 child<> 和 link<> (因爲其類型就是這樣構造的,後文簡稱child和link) 。
child
child實現了composition關係,表示一個設備(parent)創建了另外一個設備(child),parent掌控child的生命週期,負責向其發送事件。一個device只能有一個parent,但能有多個child。這樣就構成一棵組合樹。
通過 object_property_add_child 添加child:
=> object_property_add 將 child 作爲 obj 的屬性,屬性名name,類型爲 "child<child的類名>",同時getter爲object_get_child_property,沒有setter | |
=> child->parent = obj |
例如 x86_cpu_realizefn => x86_cpu_apic_create => object_property_add_child(OBJECT(cpu), "lapic", OBJECT(cpu->apic_state), &error_abort) 將創建 APICCommonState ,並設置爲 X86CPU 的child。
可以在qemu hmp查詢到:
(qemu) info qom-tree | |
/machine (pc-q35-2.8-machine) | |
/unattached (container) | |
/device[0] (host-x86_64-cpu) | |
... |
link
link實現了backlink關係,表示一個設備引用了另外一個設備,是一種鬆散的聯繫。兩個設備之間能有多個link關係,可以進行修改。它完善了組合樹,使其構成構成了一幅有向圖。
通過 object_property_add_link 添加link:
=> 創建 LinkProperty ,填充目標(child)的信息 | |
=> object_property_add 將 LinkProperty 作爲 obj 的屬性,屬性名name,類型爲 "link<child的類名>",同時getter爲 object_get_link_property 。如果傳入了check函數,則需要回調,設置setter爲 object_set_link_property |
例如 q35 有以下link:
static void q35_host_initfn(Object *obj) | |
{ | |
object_property_add_link(obj, MCH_HOST_PROP_RAM_MEM, TYPE_MEMORY_REGION, | |
(Object **) &s->mch.ram_memory, | |
qdev_prop_allow_set_link_before_realize, 0, NULL); | |
object_property_add_link(obj, MCH_HOST_PROP_PCI_MEM, TYPE_MEMORY_REGION, | |
(Object **) &s->mch.pci_address_space, | |
qdev_prop_allow_set_link_before_realize, 0, NULL); | |
object_property_add_link(obj, MCH_HOST_PROP_SYSTEM_MEM, TYPE_MEMORY_REGION, | |
(Object **) &s->mch.system_memory, | |
qdev_prop_allow_set_link_before_realize, 0, NULL); | |
object_property_add_link(obj, MCH_HOST_PROP_IO_MEM, TYPE_MEMORY_REGION, | |
(Object **) &s->mch.address_space_io, | |
qdev_prop_allow_set_link_before_realize, 0, NULL); | |
} |
將 Q35PCIHost 和 ram_memory / pci_address_space / system_memory / address_space_io 鏈接起來。
API
根據前面所述,屬性有兩種定義方式,一種是通過 DEFINE_PROP_*
定義,另一種是通過 object_property_add_<type>
進行定義。根據不同的定義方式,set會不同,設置值的方式也有所不同。
object_property_set_<type>
用於設置某個屬性的值。比如 object_property_set_bool :
=> qbool_from_bool 將要設置的值包裝成相應的 QObject ,這裏是QBool | |
=> object_property_set_qobject | |
=> qobject_input_visitor_new 將傳入的QObject包裝成Visitor,其中含各類型的處理函數 | |
=> object_property_set => object_property_find 從props的hash table中找到對應的 ObjectProperty | |
=> prop->set |
對於 DEFINE_PROP_BOOL 創建的屬性來說,其閉包爲qdev_prop_bool,因此在初始化時 set 被設置爲 set_bool
set_bool | |
=> qdev_get_prop_ptr 將設備指針加上屬性值在其中的偏移量,得到屬性值的地址 | |
=> visit_type_bool => v->type_bool (qobject_input_type_bool) => qobject_input_get_object 從Visitor中取出QObject | |
=> qbool_get_bool 從QObject中取出值,設置到屬性值的地址 |
對於 object_property_add_bool 創建的屬性來說,它在 object_property_add 時設置 set 爲 property_set_bool
property_set_bool | |
=> visit_type_bool => v->type_bool (qobject_input_type_bool) => qobject_input_get_object 找到QObject | |
=> (BoolProperty)prop->set 調用setter |
比如 device 類型的 instance_init 即 device_initfn 中,定義了 realized 屬性:
object_property_add_bool(obj, "realized", device_get_realized, device_set_realized, NULL) |
於是 setter 爲 device_set_realized
=> dc->realize 調用realize函數,其在 class_init 中定義 | |
=> dev->realized = value 設置類實例對象的成員 |
一句話總結,前者的屬性值的設置由 type_bool 負責設置,而後者由 setter 負責設置。
object_property_get_<type>
用於讀取某個屬性的值。比如 object_property_get_bool :
=> object_property_get_qobject | |
=> 創建空的QObject指針 | |
=> qobject_output_visitor_new 將傳入的QObject包裝成Visitor,其中含各類型的處理函數 | |
=> object_property_get => object_property_find 從props的hash table中找到對應的 ObjectProperty | |
=> prop->get 調用get函數,設置QObject | |
=> qobject_to_qbool 將QObject轉成QBool | |
=> qbool_get_bool 從QBool中取出值,返回 |
對於 DEFINE_PROP_BOOL 創建的屬性來說,其閉包爲qdev_prop_bool,因此在初始化時 get 被設置爲 get_bool
get_bool | |
=> qdev_get_prop_ptr(dev, prop) 將設備指針加上屬性值在其中的偏移量,得到屬性值的地址 | |
=> visit_type_bool => v->type_bool (qobject_output_type_bool) => qobject_input_get_object 將屬性值包裝成QObject |
對於 object_property_add_bool 創建的屬性來說,它在 object_property_add 時設置 get 爲 property_get_bool :
property_get_bool | |
=> prop->get 調用getter,得到屬性值 | |
=> visit_type_bool => v->type_bool (qobject_output_type_bool) => qobject_input_get_object 將屬性值包裝成QObject |
個人的理解是,set 和 get 都需要通過 QObject 和 Visitor 兩層包裝。前者把要設置屬性值包裝成QObject再到Visitor,然後再取出設置到相應地址。後者根據屬性值地址將屬性值包裝成QObject,設置爲Visitor中QObject指針指向,然後再從QObject中取出值。
object_property_parse
在用一個string設置不知道類型的屬性的值時,使用 object_property_parse:
void object_property_parse(Object *obj, const char *string, | |
const char *name, Error **errp) | |
{ | |
Visitor *v = string_input_visitor_new(string); | |
object_property_set(obj, v, name, errp); | |
visit_free(v); | |
} |
它會創建一個 Visitor 並將值設置到裏面,這裏定義了string轉其他類型屬性的函數:
v->visitor.type = VISITOR_INPUT; | |
v->visitor.type_int64 = parse_type_int64; | |
v->visitor.type_uint64 = parse_type_uint64; | |
v->visitor.type_size = parse_type_size; | |
v->visitor.type_bool = parse_type_bool; | |
v->visitor.type_str = parse_type_str; | |
v->visitor.type_number = parse_type_number; |
總結
如此一來,根據 TypeInfo 創建了 TypeImpl ,然後根據 TypeImpl 創建了對應的 ObjectClass ,再根據 TypeImpl 創建了對應的 Object , ObjectClass 和 Object 都有自己的 Property,關係如下:
TypeImpl | |
class -> ObjectClass(AccelClass) Object(AccelState) | |
<- type <- class | |
TypeImpl <- parent_type properties(GHashTable) properties(GHashTable) |