本文基于QEMU 7.0。
QEMU中同一类别的设备可能有很多种,对于同类不同种的设备,需要调用不同的函数进行相应的处理;并且,同一总线下可能挂在了很多不同种,甚至不同类别的设备,也需要调用不同的函数进行相应的处理。为了能方便的处理这些情况,面向对象的思想是必不可少的。为此,QEMU中实现了一套面向对象的模型——QOM(QEMU Object Module)。
QOM实现了类似JAVA的单根继承的结构,可实现多个接口,并可对对象的属性进行动态的增加、移除、修改。其中比较重要的几个结构如下:
- Object: 所有可实例化的对象的基类;
- ObjectClass: 所有类对象的基类;
- Interface: 所有接口的基类;
- ObjectProperty: 对象可动态增加、移除、修改的属性;
- TypeInfo: 保存对应类型的信息(包括继承关系、实现的接口、是否是抽象类、类对象的初始化函数、实例的初始化函数(构造函数?)等);
- TypeImpl: TypeInfo的进一步抽象,以此来初始化对应的类对象及对应的示例。该结构对用户是透明的,用户只需定义TypeInfo。
每个结构的具体信息及其字段的具体含义可在qemu/include/qom/object.h中查看。它们之间的关系如以下的UML所示:
若想要新增一种类,则需要实现以下几个结构体:
- 新增一个
Object:若为接口,则只需typedef一下即可,如typedef struct MyInterface MyInterface;;否则的话,该结构体中放置对象的各种各成员变量,其中第一个成员必须是Object或其子类; - 新增一个
ObjectClass:该结构体放置对象的各种成员函数和类成员变量,其中第一个成员必须是ObjectClass或其子类(若为接口,则第一个成员必须是Interface; TypeInfo:该结构体主要用于注册该类的typename、继承关系、构造析构函数等。
QOM中面向对象的特性
利用QOM,QEMU实现了在C语言中编写面向对象风格的代码。虽然使用起来,与C++、JAVA这种原生支持面向对象的语言略复杂了些,但至少封装、继承、多态三大面向对象的特性都可以实现。
封装
封装自不用说,C语言中的结构体就能实现封装;当然, 像public、protected、private这种访问属性是没法实现了,虽说将对象中部分数据的类型定义放到.c文件中或将数据定义为void *opaque也能实现类似private的能力,但是这样代码的可阅读性和可维护性就大大降低了,因此对于数据访问属性的控制更多还是靠代码中的约定。如DeviceState定义中的parent_obj是private的,你不应该直接访问它。
struct DeviceState {
/*< private >*/
Object parent_obj;
/*< public >*/
const char *id;
char *canonical_path;
bool realized;
bool pending_deleted_event;
QemuOpts *opts;
int hotplugged;
BusState *parent_bus;
QLIST_HEAD(, NamedGPIOList) gpios;
QLIST_HEAD(, BusState) child_bus;
int num_child_bus;
int instance_id_alias;
int alias_required_for_version;
};继承
继承分为对类的泛化和对接口的实现,这两者在QOM中主要是通过定义结构体TypeInfo中的成员实现的。在TypeInfo中通过指定name和parent分别指定该类及其父类的名字(应该说是type name或者type id,需保证全局唯一),在interfaces中指定该类实现的接口的名字。同时,为了向上类型转换和向下类型转换方便,还需要分别将该类父类对应的Object和ObjectClass结构体作为该类对应的Object和ObjectClass结构体中的首位成员。
interface Interface0 {
}
interface Interface1 {
}
abstract class Base extends Object {
}
class Derived0 extends Base implements Interface0 {
}
class Derived1 extends Base implements Interface0, Interface1 {
}typedef struct Interface0 Interface0;
typedef struct Interface0Class {
InterfaceClass parent_class;
};
static const TypeInfo interface0_info {
.name = "Interface0",
.parent = TYPE_INTERFACE,
};
typedef struct Interface1 Interface1;
typedef struct Interface1Class {
InterfaceClass parent_class;
};
static const TypeInfo interface1_info {
.name = "Interface1",
.parent = TYPE_INTERFACE,
};
typedef struct Base {
Object parent_obj;
};
typedef struct BaseClass {
ObjectClass parent_class;
};
static const TypeInfo base_info {
.name = "Base",
.parent = TYPE_OBJECT,
.abstract = true,
};
typedef struct Drived0 {
Base parent_obj;
};
typedef struct Drived0Class {
BaseClass parent_class;
};
static const TypeInfo drived0_info {
.name = "Drived0",
.parent = "Base",
.instance_size = sizeof(Drived0),
.interfaces = (InterfaceInfo[]) {
{ "Interface0" },
{ },
},
};
typedef struct Drived1 {
Base parent_obj;
};
typedef struct Drived1Class {
BaseClass parent_class;
};
static const TypeInfo drived1_info {
.name = "Drived1",
.parent = "Base",
.instance_size = sizeof(Drived1),
.interfaces = (InterfaceInfo[]) {
{ "Interface0" },
{ "Interface1" },
{ },
},
};说到继承就不得不说一下类型转换,尤其是安全的向下类型转换。由于QEMU本身使用C语言实现的,而C语言本身不支持OO,因此不能像C++或JAVA那样使用.和->访问父类的成员,只能先将对象向上转换为父类对象,然后再访问父类的成员。此外,C语言也无法做到给成员函数自动传递this指针(或其他类似的概念),只能再成员函数的参数中增加一个指向该对象的指针,调用时手动传递该对象的指针,而这个参数类型只能为指向父类的指针。若子类中的override这个成员函数,常常需要访问子类的成员,因此需要将该参数向下转换为指向子类的指针。由于以上两点原因,QEMU中涉及到QOM的代码中类型转换的次数非常多。QOM中为了能更方便、安全地进行类型转换,提供了下面的helper 函数:
/**
* object_dynamic_cast:
* @obj: The object to cast.
* @typename: The @typename to cast to.
*
* This function will determine if @obj is-a @typename. @obj can refer to an
* object or an interface associated with an object.
*
* Returns: This function returns @obj on success or #NULL on failure.
*/
Object *object_dynamic_cast(Object *obj, const char *typename);
/**
* object_dynamic_cast_assert:
*
* See object_dynamic_cast() for a description of the parameters of this
* function. The only difference in behavior is that this function asserts
* instead of returning #NULL on failure if QOM cast debugging is enabled.
* This function is not meant to be called directly, but only through
* the wrapper macro OBJECT_CHECK.
*/
Object *object_dynamic_cast_assert(Object *obj, const char *typename,
const char *file, int line, const char *func);这两个函数都可以进行安全的动态类型转换,但主要有以下2点不同:
- 当
obj不是指向typename类型的指针时,object_dynamic_cast会返回NULL;而object_dynamic_cast_assert则会调用assert()使得当前程序退出; object_dynamic_cast只是做了类型检查,而object_dynamic_cast_assert除此之外还将(成功的)类型转换的结果缓存了下来以供下次类型转换使用。因此,当某种类型频繁地转换为另一种类型时,使用object_dynamic_cast_assert会更快。
由于多数情况下,当obj不是我们期望的类型时,说明代码逻辑出了问题,我们无法进行进一步的处理,只能退出程序,因此object_dynamic_cast_assert更常用一些。而QOM也用宏封装了这个函数,方便我们调用:
/**
* OBJECT_CHECK:
* @type: The C type to use for the return value.
* @obj: A derivative of @type to cast.
* @name: The QOM typename of @type
*
* A type safe version of @object_dynamic_cast_assert. Typically each class
* will define a macro based on this type to perform type safe dynamic_casts to
* this object type.
*
* If an invalid object is passed to this function, a run time assert will be
* generated.
*/
#define OBJECT_CHECK(type, obj, name) \
((type *)object_dynamic_cast_assert(OBJECT(obj), (name), \
__FILE__, __LINE__, __func__))一般地,每一种QOM对象都会对OBJECT_CHECK再次封装,如#define ARM_CPU(obj) OBJECT_CHECK(ARMCPU, (obj), TYPE_ARM_CPU)。
多态
每个类对应的ObjectClass结构体类似C++中的虚函数表和类成员变量,因此QOM中多态的实现关键就是如何正确地初始化ObjectClass中的函数指针。这一步是在TypeInfo中的class_init回调函数中实现的,在每个类的第一个实例初始化时,会调用对应类的class_init回调函数先初始化ObjectClass。由于父类和子类Object中的class会指向不同的ObjectClass,因此若子类想要重写(override)父类中的函数,只需要在class_init回调函数中将ObjectClass中相应的成员函数赋值为子类自己实现的函数即可。
构造
在QOM中,对象的构造(初始化)分为以下4步:
- 注册
TypeInfo; - 根据
TypeInfo注册TypeImpl; - 根据
TypeImpl构造类对象; - 根据
TypeImpl构造对象。
其中,对于同一种对象的多个实例来说,1~3步只会执行一次。比如,在第一次构造TYPE_ARM_HOST_CPU对象时,1~4步都会执行;而之后再次构造TYPE_ARM_HOST_CPU对象时,则只会执行第4步。
注册TypeInfo
由于C语言本身不支持OO,无法像在JAVA中用类似class Derived0 extends Base implements Interface0的方式声明一个类并指定继承关系、实现关系等,因此若要在C语言中实现OO则必须要有数据结构来保存这些信息。在QOM中,这部分信息需要利用TypeInfo这个结构体来提供。TypeInfo具体的字段及其含义如下:
name:该类的typename,必须;parent:父类的typename,必须:abstract:是否为抽象类,若为抽象类则不能实例化(默认不是抽象类);class_size:类对象的大小,若为0则与父类类对象的大小相同(默认与父类类对象大小相同);class_init:类对象构造函数,虚函数表的初始化需在此完成;QOM中调用顺序为先调用父类类对象的构造函数,再调用子类类对象的构造函数;class_base_init:在构造子类类对象时,先构造父类类对象,然后将父类类对象拷贝到子类类对象中,而这种内存拷贝可能会造成副作用,此时class_base_init可用来抵消这种副作用;QOM中会在调用完所有父类类对象构造函数之后、调用子类类对象构造函数之前调用class_base_init,调用顺序为按照从子类到父类的顺序调用所有父类类对象的class_base_init(但不会调用本身类对象的class_base_init);class_data:传递给类对象构造函数class_init和class_base_init的入参;interfaces:该类实现的接口列表,列表最后一个元素需为空;instance_size:实例的大小,若为0则与父类实例的大小相同;instance_init:实例构造函数,与OO语言相同,除非与父类成员变量初始化的值不同,否则只需初始化子类的成员变量即可;QOM中调用顺序为先调用父类实例的构造函数,再调用子类实例的构造函数;instance_post_init:在调用完所有的构造函数后,会调用子类及父类的instance_post_init来结束初始化的流程;QOM中会在调用完成所有构造函数后调用instance_post_init,顺序为先调用子类的instance_post_init,再调用父类的instance_post_init;instance_finalize:实例析构函数,与OO语言相同,只需释放子类的成员变量,不需释放父类实例的成员变量,也不需释放子类实例(否则会造成double free);QOM中调用顺序为先调用子类实例的析构函数,再调用父类实例的析构函数。
用户还需要通过type_register_static和type_init来注册TypeInfo。比如上文继承中示例代码中的类可以通过以下方式来注册对应的TypeInfo:
static void register_types(void)
{
type_resgister_static(&interface0_info);
type_resgister_static(&interface1_info);
type_resgister_static(&base_info);
type_resgister_static(&drived0_info);
type_resgister_static(&drived1_info);
}
type_init(register_types)根据TypeInfo注册TypeImpl
上一节中type_register_static会根据TypeInfo生成对应的TypeImpl并将其加入到全局的哈希表中,便于后续根据类型名(即TypeInfo中的name)查找到对应的TypeImpl,而TypeImpl才是真正保存类型信息的结构,TypeInfo只是为了方便开发者。而type_init则是注册对应的register函数,但没有调用这个函数(type_init是通过宏生成一个__attribute__((constructor))的函数,在该函数中将参数传入的函数加入到一个链表中,__attribute__((constructor))属性可以保证生成的函数会在main之前被调用)。在main函数中通过module_call_init(MODULE_INIT_QOM)调用通过type_init注册的函数,也就是实现了根据TypeInfo注册TypeImpl。
根据TypeImpl构造类对象
个人理解,类对象就是一个存放该类的实例共用的信息的结构,比如callback(这个功能类似C++中的虚函数表)、属性。因此一个类只需要一个类对象,该类的所有实例共用这一个类对象。在该类实例化第一个实例对象时,会首先通过type_initialize实例化类对象,并将TypeImpl中的class指向该类对象。
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);
/* Any type with zero instance_size is implicitly abstract.
* This means interface types are all abstract.
*/
if (ti->instance_size == 0) {
ti->abstract = true;
}
if (type_is_ancestor(ti, type_interface)) {
...
}
ti->class = g_malloc0(ti->class_size);
parent = type_get_parent(ti);
if (parent) {
// 如果该类存在父类,则首先初始化父类的类对象,再将父类的类对象拷贝过来
type_initialize(parent);
...
memcpy(ti->class, parent->class, parent->class_size);
...
} else {
ti->class->properties = g_hash_table_new_full(
g_str_hash, g_str_equal, g_free, object_property_free);
}
ti->class->type = ti;
// 上述拷贝父类类对象时如果存在副作用,则通过class_base_init消除
while (parent) {
if (parent->class_base_init) {
parent->class_base_init(ti->class, ti->class_data);
}
parent = type_get_parent(parent);
}
// 调用class_init初始化类对象;由于上述已经实例化父类的类对象了,因此class_init的调用顺序是先调用父类的class_init,再调用子类的class_init
if (ti->class_init) {
ti->class_init(ti->class, ti->class_data);
}
}根据TypeImpl构造对象
当构造对象时,首先根据类型名查找到对应的TypeImpl,分配一段对应大小(大小为type->instance_size)的内存后调用object_initialize_with_type进行对象的初始化。object_initialize_with_type首先会利用obj->calss = type->class将对象与其对应的类对象关联起来,然后调用object_init_with_type来依次调用构造函数(instance_init)来完成初始化。
static void object_initialize_with_type(void *data, size_t size, TypeImpl *type)
{
Object *obj = data;
...
memset(obj, 0, type->instance_size);
obj->class = type->class;
object_ref(obj);
...
object_init_with_type(obj, type);
object_post_init_with_type(obj, type);
}
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);
}
}析构
QOM的对象在构造出来之后需要依赖引用计数来进行内存的管理,这需要开发者手动调用object_ref/object_unref,当引用计数达到0时,则会调用object_finalize进行对象的析构,析构时首先会调用子类的析构函数,再调用父类的析构函数,最后调用free释放该对象的内存。
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));
}
}
static void object_finalize(void *data)
{
Object *obj = data;
TypeImpl *ti = obj->class->type;
object_property_del_all(obj);
object_deinit(obj, ti);
g_assert(obj->ref == 0);
if (obj->free) {
obj->free(obj);
}
}
发表回复