qemu学习笔记:QOM补充

2.4 QOM介绍

说明:小白学习qemu的一些学习笔记。主要是学习《QEMU&KVM源码解析与应用》这本书。
参考:
《QEMU&KVM源码解析与应用》作者:李强
Qemu - 百问网嵌入式Linux wiki

2.4.3 类型的层次结构

上一节中type_initialize类型初始化需要先对父类型初始化。本节的层次结构就是为了实现这一功能,QOM就是通过层次结构实现了类似C++中的继承概念。

edu设备类型信息的结构体里,开源找到edu设备的父类型是TYPE_PCI_DEVICE,说明edu设备是一个pci设备。

hw/misc/edu.c   
static const TypeInfo edu_info = {
        .name          = "edu",
        .parent        = TYPE_PCI_DEVICE,
    };
找TYPE_PCI_DEVICE找到了pci.h
    
hw/pci/pci.c 
static const TypeInfo pci_device_type_info = {
    .name = TYPE_PCI_DEVICE,
    .parent = TYPE_DEVICE,
};

hw/core/qdev.c
static const TypeInfo device_type_info = {
    .name = TYPE_DEVICE,
    .parent = TYPE_OBJECT,
};

qom/object.c
static TypeInfo interface_info = {
    .name = TYPE_INTERFACE,
    .class_size = sizeof(InterfaceClass),
    .abstract = true,
};
static TypeInfo object_info = {
    .name = TYPE_OBJECT,
    .instance_size = sizeof(Object),
    .instance_init = object_instance_init,
    .abstract = true,
};

在 QEMU 的类型系统(QOM)中,TYPE_OBJECT 是所有类型的根基类:

  1. 根节点TYPE_OBJECT 是 QEMU 类型系统的根,所有类型最终继承自它。

  2. 直接子类型:图中列出了 TYPE_OBJECT 的 6 个直接子类型,涵盖设备、总线、虚拟机等核心抽象。

    来自deepseek:
    	动态性:具体子类型可能因 QEMU 版本、编译配置(如启用的架构或设备)而有所不同。
    	以下是 TYPE_OBJECT 的直接子类型示意图:
    TYPE_OBJECT
    ├── TYPE_DEVICE        ## 所有设备基类(如 PCI、ISA 设备)
    ├── TYPE_BUS           ## 总线基类(如 PCI 总线、ISA 总线)
    ├── TYPE_MACHINE       ## 虚拟机类型基类(如 x86、ARM 机器)
    ├── TYPE_CPU           ## CPU 基类(如 x86、ARM CPU)
    ├── TYPE_INTERFACE     ## 接口基类(支持多继承,如 HotplugHandler)
    └── TYPE_MODULE        ## 模块基类(管理动态加载的插件或驱动)
    

接下来从数据结构角度讨论类型的层次结合。

在类型的初始化函数type_initialize()中,分配了类型的class结构,代表了类型的信息。类似于C++中的一个类。如果本身没有定义class_size就会使用父类型的class_size进行初始化。下面以edu设备为例分析。

static void type_initialize(TypeImpl *ti)
{
    memcpy(ti->class, parent->class, parent->class_size);
}

image-20250503112200346

父类型的成员域是在什么是初始化的呢?

type_initialize中调用下面代码对父类型站的这部分空间进行初始化。

static void type_initialize(TypeImpl *ti)
{
    memcpy(ti->class, parent->class, parent->class_size);
}
    ti->class			: 目标地址,指向当前类型的类结构内存。
    parent->class		: 源地址,指向父类的类结构内存。
    parent->class_size	: 复制的字节数,即父类类结构的大小。

再看type_initialize函数最后一句

ti->class_init(ti->class, ti->class_data);
ti->class : 分配的`PCIDeviceClass`结构体。

其中,ti->class对于edu设备而言是一个刚刚分配的PCIDeviceClass结构体。然而,class_init回调函数期望的第一个参数类型是ObjectClass,因此需要进行从ObjectClassPCIDeviceClass的类型转换。

image-20250503114304613

为什么edu_class_init(),第一个参数设计成ObjectClass

  • 隐式转换:因父类结构体位于子类起始位置,PCIDeviceClass*ObjectClass* 的转换是内存安全的。
  • 显式回退:在回调函数中,需通过类型检查宏(如 PCI_DEVICE_CLASS)将基类指针转换回具体子类指针,以访问子类特有字段。
  • 设计意义:QOM 通过此机制实现了类似面向对象的继承和多态,同时保持 C 语言的高效性。

转换的原理

  • 内存布局兼容性
    C 语言中,子类结构体的第一个成员必须是父类结构体。例如:

    image-20250503115148535

    typedef struct PCIDeviceClass {
        DeviceClass parent_class;  // 第一个成员是父类 DeviceClass
        // PCI 特有字段...
    } PCIDeviceClass;
    
    typedef struct DeviceClass {
        ObjectClass parent_class;  // 第一个成员是基类 ObjectClass
        // 设备通用字段...
    } DeviceClass;
    

    因此,PCIDeviceClass* 的指针可以直接视为 DeviceClass*ObjectClass*,无需显式转换。

  • 类型安全性
    QEMU 通过类型注册机制确保转换的合法性。例如,PCIDeviceClass 在注册时会声明其父类为 TYPE_DEVICE,从而建立继承链。

关键机制

  1. 类型转换宏
    QEMU 提供类型检查宏(如 PCI_DEVICE_CLASS),其底层实现为:

    ##define PCI_DEVICE_CLASS(klass) \
        OBJECT_CLASS_CHECK(PCIDeviceClass, (klass), TYPE_PCI_DEVICE)
    
    • OBJECT_CLASS_CHECK:验证 klass 的类型是否为 TYPE_PCI_DEVICE 或其子类。
    • 若类型匹配,返回 PCIDeviceClass* 指针;否则触发断言错误。
  2. 多态性的实现

    • 父类指针(如 ObjectClass*)可指向任意子类实例。
    • 子类通过覆盖父类的函数指针(如 resetrealize)实现多态行为。

2.4.4 对象的构造与初始化

本节主要是说明对象初始化。对应的是下图调用instance_init()

image-20250501222624528

运行流程为

// 1、从名字找到TypeImpl,进入object_new_with_type
Object *object_new(const char *typename)
{
    TypeImpl *ti = type_get_by_name(typename);

    return object_new_with_type(ti);
}
// 2、分配对象大小
Object *object_new_with_type(Type type)
{
    Object *obj;

    g_assert(type != NULL);
    // 判断类初始化是否完成
    type_initialize(type);
    // 对象分配大小
    obj = g_malloc(type->instance_size);
    object_initialize_with_type(obj, type->instance_size, type);
    obj->free = g_free;

    return obj;
}

// 对对象初始化
void object_initialize_with_type(void *data, size_t size, TypeImpl *type)
{
    Object *obj = data;

    g_assert(type != NULL);
    type_initialize(type);

    g_assert_cmpint(type->instance_size, >=, sizeof(Object));
    g_assert(type->abstract == false);
    g_assert_cmpint(size, >=, type->instance_size);

    memset(obj, 0, type->instance_size);
    obj->class = type->class;
    object_ref(obj);
    obj->properties = g_hash_table_new_full(g_str_hash, g_str_equal,
                                            NULL, object_property_free);
    object_init_with_type(obj, type);
    object_post_init_with_type(obj, type);
}

// 递归调用所有父类型的对象初始化函数和自身对象的初始化函数
// 这里是调用父类的instance_init函数
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);
    }
}
// 调用instance_post_init完成对象初始化之后的工作
static void object_post_init_with_type(Object *obj, TypeImpl *ti)
{
    if (ti->instance_post_init) {
        ti->instance_post_init(obj);
    }

    if (type_has_parent(ti)) {
        object_post_init_with_type(obj, type_get_parent(ti));
    }
}

QOM 对象构造相关要点

  1. 类型的构造(注册)
    • 方式:通过TypeInfo构造一个TypeImpl的哈希表。
    • 时机:在main之前完成。
  2. 类型初始化(先调父类构造,再class_init
    • 进行位置:在main函数中。
    • 作用范围:全局,所有编译进去的 QOM 对象都会调用。
  3. 类对象构造和初始化(调instance_init
    • 构建内容:构造具体的对象实例。
    • 触发条件:仅在命令行指定对应设备时,才会创建对象。
  4. 对象实例化:
    • 以上过程,构造出了对象并调用初始化函数,但是 EduState 里面的数据内容并没有填充,这个时候的 edu 设备状态并不是可用的
    • 对设备而言还需要设置它的 realized 属性为 true 才行

2.4.5 属性

来到了最后一步——实例化:把realized属性设置成true。

object_property_set_bool(OBJECT(dev), true, "realized", &err);

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

void object_property_set_qobject(Object *obj, QObject *value,
                                 const char *name, Error **errp)
{
    Visitor *v;
    /* TODO: Should we reject, rather than ignore, excess input? */
    v = qobject_input_visitor_new(value, false);
    object_property_set(obj, v, name, errp);
    visit_free(v);
}

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

image-20250501222624528

1. 类属性与对象属性

类别类属性(Class Property)对象属性(Object Property)
存储位置ObjectClass->properties(类型级别)Object->properties(实例级别)
初始化时机类型初始化阶段(type_initialize对象实例化阶段(object_initialize_with_type
用途定义类型的共享元数据或方法(如默认配置)管理实例特有状态或参数(如动态配置)
示例设备类型的realize方法、desc描述字段设备的realized状态、mmio_size参数
继承性支持继承,子类可覆盖父类属性不参与继承,仅作用于当前实例
  • 类属性:存在于ObjectClass的properties中。是一个哈希表(键:属性名,值:ObjectProperty)。在对象的初始化函数type_initialize中构造

    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; // 对象与父对象解除关系时的回调函数
        GHashTable *properties;   // 哈希表,存储类属性(键:属性名,值:ObjectProperty)
    };
    
  • 对象属性:存放到Object的properties中。是一个哈希表(键:属性名,值:ObjectProperty)。在对象的初始化函数object_initialize_with_type中构造

    struct Object
    {
        /*< private >*/
        ObjectClass *class;      // 对象所属的类(类型信息、方法定义)
        ObjectFree *free;        // 析构回调函数,用于释放对象资源(引用计数归零时调用)
        GHashTable *properties;  // 哈希表,存储对象属性(键:属性名,值:ObjectProperty)
        uint32_t ref;            // 引用计数,管理对象生命周期
        Object *parent;          // 父对象指针,用于构建对象层次关系或设备树
    };
    

上面提到了类属性和对象属性都是存在哈希表里面,key都是属性名,value都是ObjectProperty。属性都是由ObjectProperty来表示的。

typedef struct ObjectProperty
{
    gchar *name;                   // 属性名称(如 "realized"、"mmio_size")
    gchar *type;                   // 属性类型(如 "bool"、"string"、"link")
    gchar *description;            // 属性描述(用于文档或帮助信息)
    ObjectPropertyAccessor *get;   // 获取属性值的回调函数
    ObjectPropertyAccessor *set;   // 设置属性值的回调函数
    ObjectPropertyResolve *resolve;// 解析属性依赖或链接的回调函数(可选)
    ObjectPropertyRelease *release;// 释放属性资源的回调函数(可选)
    void *opaque;                  // 指向具体属性类型的结构体(如 BoolProperty)
} ObjectProperty;

下面针对void *opaque;这个指向具体属性类型的结构体,QOM 中不同类型的属性通过具体结构体定义其行为,以下是核心类型:

LinkProperty

用途

  • 管理对象间的父子关系(如设备树中的层级结构)。
  • 示例:PCI 设备与其功能部件的链接。
定义于 qom/object.c:

typedef struct {
    Object **child;                    // 指向子对象的指针(维护对象间的链接关系)
    void (*check)(const Object *, const char *, Object *, Error **);  // 链接有效性检查函数
    ObjectPropertyLinkFlags flags;     // 链接属性标志(如是否允许动态修改)
} LinkProperty;
```
typedef struct {
    Object **child;                    // 指向子对象的指针(维护对象间的链接关系)
    void (*check)(const Object *, const char *, Object *, Error **);  // 链接有效性检查函数
    ObjectPropertyLinkFlags flags;     // 链接属性标志(如是否允许动态修改)
} LinkProperty;

StringProperty

用途

  • 管理字符串类型的配置参数或状态(如设备的描述信息)。
  • 示例:网卡的 mac_address 属性。
typedef struct StringProperty {
    char *(*get)(Object *, Error **);   // 获取字符串值的回调函数
    void (*set)(Object *, const char *, Error **); // 设置字符串值的回调函数
} StringProperty;

BoolProperty

用途

  • 管理布尔类型的开关或状态(如设备的启用/禁用状态)。
  • 示例:设备的 realized 属性(标记设备是否已初始化)
typedef struct BoolProperty {
    bool (*get)(Object *, Error **);    // 获取布尔值的回调函数
    void (*set)(Object *, bool, Error **); // 设置布尔值的回调函数
} BoolProperty;

image-20250503193455322

Object
├── properties (GHashTable)
│   └── [属性名] → ObjectProperty
│       ├── name: "realized"
│       ├── type: "bool"
│       ├── set: property_set_bool      // 通用设置函数
│       ├── get: property_get_bool      // 通用获取函数
│       └── opaque: → BoolProperty      // 绑定到具体属性结构体
│           ├── get: edu_get_realized   // 具体设备的 get 逻辑
│           └── set: edu_set_realized   // 具体设备的 set 逻辑

接下来,以对象属性为例来介绍属性添加和属性设置。

属性添加

就以object_property_set_bool(OBJECT(dev), true, "realized", &err);为例,在哪里给edu设备添加realized属性的?

  • 设备对对象初始化时,即调用 .instance_init = edu_instance_init,时,会先调用父类型的初始化调用他们的 .instance_init函数。

  • edu依次向上找:

    image-20250503223119795

  • 到这里已经找到在TYPE_DEVICE这个类型的时候,在instance_init里面添加了realized属性。接下来就是获取属性值device_get_realized和设置属性device_set_realized这两个函数

    1. device_get_realized 函数

      • 作用:返回设备是否已初始化(realized)。
      • 实现:通常直接返回设备状态字段(如 dev->realized)。
      static bool device_get_realized(Object *obj, Error **errp) {
          DeviceState *dev = DEVICE(obj);
          return dev->realized;
      }
      
    2. device_set_realized 函数

      • 作用:设置设备初始化状态。
        • 当设置为 true 时,触发设备的 realize 方法,完成资源分配和初始化。
        • 当设置为 false 时,触发 unrealize 方法,释放资源。
      • 实现:根据传入的布尔值调用设备类的 realizeunrealize 方法。
      • 这里调用了DeviceClass的realize函数
      static void device_set_realized(Object *obj, bool value, Error **errp) {
          DeviceState *dev = DEVICE(obj);
          if (value && !dev->realized) {
              // 调用 realize 方法
              if (dc->realize) {
                  dc->realize(dev, &local_err);
              }
          } else if (!value && dev->realized) {
              // 调用 unrealize 方法
              device_class_unrealize(dev);
          }
      }
      

属性设置

属性设置的函数主要是object_property_set(),这里是使用,最后一步一步到属性的set函数。也就是设置完就会执行属性的set函数。本小节的例子中代表着device_set_realized函数。

object_property_set_bool(OBJECT(dev), true, "realized", &err);

image-20250503225039308

最后跑到了device_set_realized 函数,下面就是调用 realize 方法。

父设备的 realized 属性

  • 子设备的实例化需在父设备已实例化的上下文中执行。

  • 未调用子类方法的后果
    若父类未显式调用子类的 realize 方法,子类逻辑将不会执行,可能导致设备功能缺失。
    因此,父类必须负责触发子类初始化

  • 多级继承的扩展性
    在更深的继承层次中(如祖父类→父类→子类),每一级的 realize 方法需依次调用下一级的方法,形成链式调用。

下面图示

image-20250504004330565

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值