From a2a243be11c5be87fdb487c029deb0976fbb4f9e Mon Sep 17 00:00:00 2001 From: jl-jiang Date: Sat, 26 Aug 2023 17:34:03 +0800 Subject: [PATCH 01/13] finish the article --- ...20230815-qemu-system-device-model-part1.md | 387 ++++++++++++++++++ .../DeviceClass.svg | 4 + .../init_type_list.svg | 4 + .../qdev_realize.svg | 4 + 4 files changed, 399 insertions(+) create mode 100644 articles/20230815-qemu-system-device-model-part1.md create mode 100644 articles/images/qemu-system-device-model-part1/DeviceClass.svg create mode 100644 articles/images/qemu-system-device-model-part1/init_type_list.svg create mode 100644 articles/images/qemu-system-device-model-part1/qdev_realize.svg diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md new file mode 100644 index 0000000..82c130f --- /dev/null +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -0,0 +1,387 @@ +> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2
+> Author: jl-jiang
+> Date: 2023/08/15
+> Revisor: Bin Meng
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Proposal: [【老师提案】QEMU 系统模拟模式分析](https://gitee.com/tinylab/riscv-linux/issues/I61KIY)
+> Sponsor: PLCT Lab, ISCAS + +# QEMU 设备模型简析(一):生命周期 + +## 前言 + +QEMU 系统模拟模式能够提供运行虚拟机所需的完整环境,包括 CPU,内存和外部设备等。其中,设备模拟是系统模拟模式的重要组成部分,也是系统模拟模式和用户模拟模式的本质区别之一。设备模型是 QEMU 中用于描述和实现设备模拟的软件结构和框架,它为设备提供了标准化的接口和方法,使得开发者可以轻松地为 QEMU 添加新的设备。本文将以 8.0.0 版本的 QEMU RISC-V (qemu-system-riscv64) 为例,分析介绍 QEMU 设备模型的生命周期。 + +## 设备类型注册 + +### 定义 + +QEMU 模拟的每一种设备都必须定义对应的设备类型,这个设备类型在代码中由结构体 `TypeInfo` 描述: + +```c +/* include/qom/object.h: 412 */ + +struct TypeInfo +{ + const char *name; + const char *parent; + + size_t instance_size; + size_t instance_align; + void (*instance_init)(Object *obj); + void (*instance_post_init)(Object *obj); + void (*instance_finalize)(Object *obj); + + bool abstract; + size_t class_size; + + void (*class_init)(ObjectClass *klass, void *data); + void (*class_base_init)(ObjectClass *klass, void *data); + void *class_data; + + InterfaceInfo *interfaces; +}; +``` + +该结构体的成员变量比较繁多,目前只需要关注以下两个成员: + +- **name:** 设备类型名称 +- **parent:** 父设备类型名称 + +通过这两个变量我们可以发现,QEMU 设备模型中使用名称作为设备类型的唯一标识,并且设备类型之间还存在继承关系。 + +### 注册 + +完成设备类型的定义后,必须注册这一设备类型,以通知 QEMU 系统可以支持这一类型的设备,为后续添加这一类型的设备准备条件。注册工作由 `type_register` 函数负责: + +```c +/* qom/object.c: 140 */ + +static TypeImpl *type_register_internal(const TypeInfo *info) +{ + TypeImpl *ti; + ti = type_new(info); + + type_table_add(ti); + return ti; +} + +TypeImpl *type_register(const TypeInfo *info) +{ + assert(info->parent); + return type_register_internal(info); +} +``` + +该函数通过调用 `type_new` 生成一个 `TypeInfo` 对应的 `TypeImpl` 类型,并以 `name` 为关键字添加到名为 `type_table` 的一个哈希表中。`type_table` 是 QEMU 维护的一个全局的类型哈希表,使用类型名字符串索引到具体的 `TypeImpl` 对象。 + +下面给出 `TypeImpl` 结构体的定义: + +```c +/* qom/object.c: 48 */ + +struct TypeImpl +{ + const char *name; + + size_t class_size; + + size_t instance_size; + size_t instance_align; + + void (*class_init)(ObjectClass *klass, void *data); + void (*class_base_init)(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]; +}; +``` + +通过观察不难发现 `TypeInfo` 和 `TypeImpl` 两个结构体的结构相似,绝大多数成员变量都相同,那么为什么注册过程中不直接使用,而要重新复制一遍呢?这与 QEMU 对象系统的设计理念有关,`TypeInfo` 结构体是面向 API 使用者的一个工具,使用者只有在注册设备类型的时候会使用到 `TypeInfo`,提供类型注册所需的必要信息和回调函数,此后 QEMU 会根据 `TypeInfo` 自动生成对应的 `TypeImpl` 结构体,至此 `TypeInfo` 的生命周期就结束了。换句话说,`TypeInfo` 保存静态的注册数据,而 `TypeImpl` 保存动态的运行数据,这种设计降低了系统的耦合度,提升了稳定性。 + +下面我们来分析 `type_register()` 函数的调用时机,这里就需要引申出另一个问题,就是注册函数本身也有“注册 - 执行”机制。我们以虚拟网卡设备 e1000 为例进行分析: + +```c +/* hw/e1000.c: 1822 */ + +static void e1000_register_types(void) +{ + int i; + + type_register_static(&e1000_base_info); + for (i = 0; i < ARRAY_SIZE(e1000_devices); i++) { + const E1000Info *info = &e1000_devices[i]; + TypeInfo type_info = {}; + + type_info.name = info->name; + type_info.parent = TYPE_E1000_BASE; + type_info.class_data = (void *)info; + type_info.class_init = e1000_class_init; + + type_register(&type_info); + } +} + +type_init(e1000_register_types) +``` + +函数 `e1000_register_types` 负责执行 e1000 网卡的设备类型注册,注意最后一行代码调用 `type_init` 函数对 `e1000_register_types` 函数进行注册,下面给出 `type_init` 函数的定义: + +```c +/* include/qemu/module.h: 56 */ + +#define type_init(function) module_init(function, MODULE_INIT_QOM) +``` + +`type_init` 函数实际上是通过宏的形式对 `module_init` 进行包装: + +```c +/* include/qemu/module.h: 35 */ + +#define module_init(function, type) \ +static void __attribute__((constructor)) do_qemu_init_ ## function(void) \ +{ \ + register_module_init(function, type); \ +} +``` + +这里我们需要关注的是这个修饰 `__attribute__((constructor))`,它是 GCC 的一个扩展,用于确保被修饰的函数在 `main` 函数执行之被调用。仍然以上文的 e1000 网卡为例,调用 `type_init` 时传入的函数名为 `e1000_register_types`,这个宏的作用就是生成函数`static void do_qemu_init_e1000_register_types(void)`,并保证其在 `main` 函数之前被调用,以达到自动初始化的目的。 + +下面分析 `register_module_init` 函数的行为: + +```c +/* util/module.c: 70 */ + +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); +} +``` + +QEMU 维护了一个全局的链表头数组 ` init_type_list`,分别指向不同类型的注册函数链表,具体如下图所示: + +![](images/qemu-system-device-model-part1/init_type_list.svg) + + `register_module_init` 函数会将待注册的设备类型注册函数,这里的例子中是 `e1000_register_types`,添加到 `MODULE_INIT_QOM` 下标所指向的链表尾部,完成注册工作。直到此时,真正的设备类型注册函数都没有执行,到底什么时候进行设备类型的注册呢?在[之前的文章](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230801-qemu-system-event-loop-part2.md)中,我们有分析过 QEMU 主事件循环的一般执行流程,`main` 函数会调用 `qemu_init` 函数进行初始化, `qemu_init` 在执行过程中会调用 `qemu_init_subsystems` 函数,而 `qemu_init_subsystems` 则会调用 `module_call_init` 函数,并传入参数 `MODULE_INIT_QOM`: + +```c +/* util/module.c: 755 */ + +void module_call_init(module_init_type type) +{ + ModuleTypeList *l; + ModuleEntry *e; + + if (modules_init_done[type]) { + return; + } + + l = find_type(type); + + QTAILQ_FOREACH(e, l, node) { + e->init(); + } + + modules_init_done[type] = true; +} +``` + +这里 `module_call_init` 函数作用就是找到 `MODULE_INIT_QOM` 对应的链表,然后依次执行链表项中的 `init` 成员函数,对于 e1000 网卡而言就是之前通过 `register_module_init` 注册的的 `e1000_register_types` 函数。 + +到此为止,设备类型注册的逻辑链条就算完整了。 + +## 设备类型初始化 + +设备类型注册后,在使用之前需要初始化该类型,并生成对应的 `ObjectClass` 对象。 + +这里回顾一下上文介绍的 `TypeImpl` 结构体,可以发现其中有一个名为 `class` 的 `ObjectClass` 类型的成员变量,所谓初始化其实就是初始化这个 `class` 成员,负责具体初始化工作的是 ` type_initialize` 函数: + +```c +/* qom/object.c: 286 */ + +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)) { + assert(ti->instance_size == 0); + assert(ti->abstract); + assert(!ti->instance_init); + assert(!ti->instance_post_init); + assert(!ti->instance_finalize); + assert(!ti->num_interfaces); + } + ti->class = g_malloc0(ti->class_size); + + parent = type_get_parent(ti); + if (parent) { + type_initialize(parent); + GSList *e; + int i; + + g_assert(parent->class_size <= ti->class_size); + g_assert(parent->instance_size <= ti->instance_size); + memcpy(ti->class, parent->class, parent->class_size); + ti->class->interfaces = NULL; + + for (e = parent->class->interfaces; e; e = e->next) { + InterfaceClass *iface = e->data; + ObjectClass *klass = OBJECT_CLASS(iface); + + type_initialize_interface(ti, iface->interface_type, klass->type); + } + + for (i = 0; i < ti->num_interfaces; i++) { + TypeImpl *t = type_get_by_name(ti->interfaces[i].typename); + if (!t) { + error_report("missing interface '%s' for object '%s'", + ti->interfaces[i].typename, parent->name); + abort(); + } + for (e = ti->class->interfaces; e; e = e->next) { + TypeImpl *target_type = OBJECT_CLASS(e->data)->type; + + if (type_is_ancestor(target_type, t)) { + break; + } + } + + if (e) { + continue; + } + + type_initialize_interface(ti, t, t); + } + } + + ti->class->properties = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + object_property_free); + + ti->class->type = ti; + + while (parent) { + if (parent->class_base_init) { + parent->class_base_init(ti->class, ti->class_data); + } + parent = type_get_parent(parent); + } + + if (ti->class_init) { + ti->class_init(ti->class, ti->class_data); + } +} +``` + +` type_initialize` 函数首先执行一些必要的检查,接着会设置类型的大小并创建该类型,设置类型实例的大小,为实例化设备做准备, +然后初始化父设备类型和接口类型,最后调用父设备类型的 `class_base_init` 和自己的 `class_init` 完成初始化工作。 + +此外,通过上述代码我们还可以得出两点结论: + +- QEMU 设备管理是一个树型结构 +- QEMU 设备模型采用了面向对象的思想,存在继承关系 + +本文重点关注设备的生命周期,以上两点这里不展开讨论,在后面的文章中会专题分析。 + +## 设备实例化 + +完成了设备类型的初始化,接着就要实例化设备了,这个过程由 `object_initialize` 函数实现,而 `object_initialize` 又通过调用 `object_initialize_with_type` 完成实例化工作。抽象地看,设备实例化主要完成三件事:首先,建立`Object` 和 `ObjectClass` 之间的关联;然后,递归调用父类型的 `instance_init` 函数;最后,调用自己的 `instance_init` 函数。需要注意的是,这种抽象的视角简化了设备实例化流程,也模糊了很多真实场景下的具体细节。 + +### 实例化函数 + +下面以 `DeviceClass` 为例详细分析设备的实例化过程。`DeviceClass` 是 QEMU 设备模型的核心组件,它继承自 `ObjectClass`,提供了设备的基本行为和属性的模板,是很多设备的父类,其实例化过程很有代表性。下图给出了 `DeviceClass` 有关类和对象之间的关系: + +![](images/qemu-system-device-model-part1/DeviceClass.svg) + +这里我们需要关注的是负责实例化 `DeviceClass` 的函数 ` device_initfn`: + +```c +/* hw/core/qdev.c: 652 */ + +static void device_initfn(Object *obj) +{ + DeviceState *dev = DEVICE(obj); + + if (phase_check(PHASE_MACHINE_READY)) { + dev->hotplugged = 1; + qdev_hot_added = true; + } + + dev->instance_id_alias = -1; + dev->realized = false; + dev->allow_unplug_during_migration = false; + + QLIST_INIT(&dev->gpios); + QLIST_INIT(&dev->clocks); +} +``` + +` device_initfn` 函数的整体执行逻辑比较简单,主要分为以下四个步骤: + +- **设备状态转换:** 将传入的 `Object` 指针转换为 `DeviceState` 指针。`DEVICE` 是一个宏,用于从基类 `Object` 转换到派生类 `DeviceState` +- **检查机器准备状态:** 使用 `phase_check` 函数检查当前的机器初始化阶段是否为 `PHASE_MACHINE_READY`。如果是,则设置设备的 `hotplugged` 属性为 1,表示该设备是在虚拟机已经启动后热插入的。同时,全局变量 `qdev_hot_added` 被设置为 `true`,表示有设备被热插入 +- **初始化设备属性:** `instance_id_alias` 被设置为 `-1`,表示没有别名;`realized` 被设置为 `false`,表示设备尚未实现;`allow_unplug_during_migration` 被设置为 `false`,表示在迁移期间不允许拔出此设备 +- **初始化设备的 GPIO 和时钟列表:** 使用 `QLIST_INIT` 宏初始化了设备的 GPIO 和时钟列表 + +### realized 属性设置 + +我们注意到 ` device_initfn` 函数将 `DeviceState` 的 `realized` 被设置为 `false`,意味着这个设备还没有被实现,那么这个属性究竟是什么时候被设置为 `Ture` 的呢?通过分析 QEMU 的启动流程,可以梳理出如下函数调用链: + +![](images/qemu-system-device-model-part1/qdev_realize.svg) + +QEMU 在初始化阶段经过一系列函数调用,最终调用 `qdev_realize` 函数,该函数是 QEMU 设备模型中的一个核心函数,用于激活设备,也就是将设备对应的 `DeviceState` 结构的 `realized` 属性设置为 `true`,表示设备已经被实例化并准备好在模拟环境中运行。在设置 `realized` 属性之前,函数会执行一些基础的检查,以确保设备尚未被实例化并且没有父总线,然后函数会检查设备总线类型并设置父总线,最后调用 `object_property_set_bool` 函数设置 `realized` 属性设置为 `true`,完成设备实例化。 + +## 设备销毁 + +设备销毁的步骤与设备创建过程正好相反,一般包含以下四个步骤: + +- **设备反实例化:** 停止操作设备并释放占用资源后,通过代码 `object_property_set_bool(OBJECT(dev), "realized", false, &error)` 设置 `realized` 属性设置为 `false`,标记设备已经反实例化 +- **从总线中移除:** 设备通常连接到一个总线,在销毁设备之前,需要从其父总线中将之移除 +- **销毁设备对象:** 调用 `object_unref` 函数减少设备对象的引用计数,当引用计数降为 0 时,并调用设备对象的析构函数,释放所占内存空间及其他资源 +- **反注册设备类型:** 有时可能还需要反注册设备类型,注销回调函数,清理释放为设备分配的各种资源 + +总而言之,设备的销毁过程可以视作设备添加过程的逆操作,除了顺序相反之外,执行流程基本相似,设备销毁的细节也不是本文关注的重点,这里仅介绍主要步骤,不再详细分析。 + +## 总结 + +本文从设备的生命周期这一角度入手,介绍了设备类型注册、设备类型初始化、设备实例化等环节的基本原理和执行流程,打通了从设备类型表创建到设备销毁的逻辑链条,在一个较为抽象的层面对 QEMU 设备模型进行了初步分析。下一篇文章中,我们将专题介绍 QEMU 对象模型以及面向对象的设备管理。 + +## 参考资料 + +- [The QEMU Object Model (QOM) ](https://www.qemu.org/docs/master/devel/qom.html) +- [浅谈QEMU的对象系统](https://juejin.cn/post/6844903845550620685) +- [understanding_qemu](https://richardweiyang-2.gitbook.io/understanding_qemu) +- 《QEMU/KVM 源码解析与应用》李强,机械工业出版社 \ No newline at end of file diff --git a/articles/images/qemu-system-device-model-part1/DeviceClass.svg b/articles/images/qemu-system-device-model-part1/DeviceClass.svg new file mode 100644 index 0000000..0da844c --- /dev/null +++ b/articles/images/qemu-system-device-model-part1/DeviceClass.svg @@ -0,0 +1,4 @@ + + + +
ObjectClass
ObjectClass
DeviceClass
DeviceClass
Object
Object
DeviceState
DeviceState
inherit
inherit
inherit
inherit
Text is not SVG - cannot display
\ No newline at end of file diff --git a/articles/images/qemu-system-device-model-part1/init_type_list.svg b/articles/images/qemu-system-device-model-part1/init_type_list.svg new file mode 100644 index 0000000..c3bbdc1 --- /dev/null +++ b/articles/images/qemu-system-device-model-part1/init_type_list.svg @@ -0,0 +1,4 @@ + + + +
MODULE_INIT_MIGRATION
MODULE_INIT_MIGRATION
MODULE_INIT_BLOCK
MODULE_INIT_BLOCK
MODULE_INIT_OPTS
MODULE_INIT_OPTS
MODULE_INIT_QOM
MODULE_INIT_QOM
MODULE_INIT_TRACE
MODULE_INIT_TRACE
MODULE_INIT_XEN_BACKEND
MODULE_INIT_XEN_BACKEND
MODULE_INIT_LIBQOS
MODULE_INIT_LIBQOS
MODULE_INIT_FUZZ_TARGET
MODULE_INIT_FUZZ_TARGET
init_type_list
init_ty...
type_register
type_register
e1000_register_types
e1000_register_types
MODULE_INIT_QOM
MODULE_INIT_QOM
ModuleEntry
ModuleE...
pc_dimm_register_types
pc_dimm_register_types
MODULE_INIT_QOM
MODULE_INIT_QOM
ModuleEntry
ModuleE...
type_register
type_register
call
call
call
call
Text is not SVG - cannot display
\ No newline at end of file diff --git a/articles/images/qemu-system-device-model-part1/qdev_realize.svg b/articles/images/qemu-system-device-model-part1/qdev_realize.svg new file mode 100644 index 0000000..2382d83 --- /dev/null +++ b/articles/images/qemu-system-device-model-part1/qdev_realize.svg @@ -0,0 +1,4 @@ + + + +
qemu_init()
qemu_init()
qemu_x_exit_preconfig()
qemu_x_exit_preconfig()
qemu_create_cli_devices()
qemu_create_cli_devices()
device_init_func()
device_init_func()
qdev_device_add()
qdev_device_add()
qdev_device_add_from_qdict()
qdev_device_add_from_qdict()
qdev_realize()
qdev_realize()
bool qdev_realize(DeviceState *dev, BusState *bus, Error **errp)
{
    assert(!dev->realized && !dev->parent_bus);

    if (bus) {
        if (!qdev_set_parent_bus(dev, bus, errp)) {
            return false;
        }
    } else {
        assert(!DEVICE_GET_CLASS(dev)->bus_type);
    }

    return object_property_set_bool(OBJECT(dev), "realized", true, errp);
}
bool qdev_realize(DeviceState *dev, BusState *bus, Error **errp)...
Text is not SVG - cannot display
\ No newline at end of file -- Gitee From fa7a31242fbaa59bfefc094c4d6e5282f71553c8 Mon Sep 17 00:00:00 2001 From: jl-jiang Date: Sat, 26 Aug 2023 17:36:30 +0800 Subject: [PATCH 02/13] qemu-system-device-model-part1.md: commit correct result of tinycorrect-spaces Signed-off-by: jl-jiang --- articles/20230815-qemu-system-device-model-part1.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index 82c130f..b4e7d54 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -1,4 +1,4 @@ -> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces]
> Author: jl-jiang
> Date: 2023/08/15
> Revisor: Bin Meng
@@ -184,7 +184,7 @@ QEMU 维护了一个全局的链表头数组 ` init_type_list`,分别指向不 ![](images/qemu-system-device-model-part1/init_type_list.svg) - `register_module_init` 函数会将待注册的设备类型注册函数,这里的例子中是 `e1000_register_types`,添加到 `MODULE_INIT_QOM` 下标所指向的链表尾部,完成注册工作。直到此时,真正的设备类型注册函数都没有执行,到底什么时候进行设备类型的注册呢?在[之前的文章](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230801-qemu-system-event-loop-part2.md)中,我们有分析过 QEMU 主事件循环的一般执行流程,`main` 函数会调用 `qemu_init` 函数进行初始化, `qemu_init` 在执行过程中会调用 `qemu_init_subsystems` 函数,而 `qemu_init_subsystems` 则会调用 `module_call_init` 函数,并传入参数 `MODULE_INIT_QOM`: + `register_module_init` 函数会将待注册的设备类型注册函数,这里的例子中是 `e1000_register_types`,添加到 `MODULE_INIT_QOM` 下标所指向的链表尾部,完成注册工作。直到此时,真正的设备类型注册函数都没有执行,到底什么时候进行设备类型的注册呢?在[之前的文章](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230801-qemu-system-event-loop-part2.md)中,我们有分析过 QEMU 主事件循环的一般执行流程,`main` 函数会调用 `qemu_init` 函数进行初始化,`qemu_init` 在执行过程中会调用 `qemu_init_subsystems` 函数,而 `qemu_init_subsystems` 则会调用 `module_call_init` 函数,并传入参数 `MODULE_INIT_QOM`: ```c /* util/module.c: 755 */ @@ -384,4 +384,4 @@ QEMU 在初始化阶段经过一系列函数调用,最终调用 `qdev_realize` - [The QEMU Object Model (QOM) ](https://www.qemu.org/docs/master/devel/qom.html) - [浅谈QEMU的对象系统](https://juejin.cn/post/6844903845550620685) - [understanding_qemu](https://richardweiyang-2.gitbook.io/understanding_qemu) -- 《QEMU/KVM 源码解析与应用》李强,机械工业出版社 \ No newline at end of file +- 《QEMU/KVM 源码解析与应用》李强,机械工业出版社 -- Gitee From e58da0d1ee6d71bf6f9d9ea39133a9bb9dd20d32 Mon Sep 17 00:00:00 2001 From: jl-jiang Date: Sat, 26 Aug 2023 17:37:03 +0800 Subject: [PATCH 03/13] qemu-system-device-model-part1.md: commit correct result of tinycorrect-codeinline Signed-off-by: jl-jiang --- ...20230815-qemu-system-device-model-part1.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index b4e7d54..43243b0 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -1,4 +1,4 @@ -> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces]
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces codeinline]
> Author: jl-jiang
> Date: 2023/08/15
> Revisor: Bin Meng
@@ -158,7 +158,7 @@ static void __attribute__((constructor)) do_qemu_init_ ## function(void) \ } ``` -这里我们需要关注的是这个修饰 `__attribute__((constructor))`,它是 GCC 的一个扩展,用于确保被修饰的函数在 `main` 函数执行之被调用。仍然以上文的 e1000 网卡为例,调用 `type_init` 时传入的函数名为 `e1000_register_types`,这个宏的作用就是生成函数`static void do_qemu_init_e1000_register_types(void)`,并保证其在 `main` 函数之前被调用,以达到自动初始化的目的。 +这里我们需要关注的是这个修饰 `__attribute__((constructor))`,它是 GCC 的一个扩展,用于确保被修饰的函数在 `main` 函数执行之被调用。仍然以上文的 e1000 网卡为例,调用 `type_init` 时传入的函数名为 `e1000_register_types`,这个宏的作用就是生成函数 `static void do_qemu_init_e1000_register_types(void)`,并保证其在 `main` 函数之前被调用,以达到自动初始化的目的。 下面分析 `register_module_init` 函数的行为: @@ -180,7 +180,7 @@ void register_module_init(void (*fn)(void), module_init_type type) } ``` -QEMU 维护了一个全局的链表头数组 ` init_type_list`,分别指向不同类型的注册函数链表,具体如下图所示: +QEMU 维护了一个全局的链表头数组 `init_type_list`,分别指向不同类型的注册函数链表,具体如下图所示: ![](images/qemu-system-device-model-part1/init_type_list.svg) @@ -216,7 +216,7 @@ void module_call_init(module_init_type type) 设备类型注册后,在使用之前需要初始化该类型,并生成对应的 `ObjectClass` 对象。 -这里回顾一下上文介绍的 `TypeImpl` 结构体,可以发现其中有一个名为 `class` 的 `ObjectClass` 类型的成员变量,所谓初始化其实就是初始化这个 `class` 成员,负责具体初始化工作的是 ` type_initialize` 函数: +这里回顾一下上文介绍的 `TypeImpl` 结构体,可以发现其中有一个名为 `class` 的 `ObjectClass` 类型的成员变量,所谓初始化其实就是初始化这个 `class` 成员,负责具体初始化工作的是 `type_initialize` 函数: ```c /* qom/object.c: 286 */ @@ -306,7 +306,7 @@ static void type_initialize(TypeImpl *ti) } ``` -` type_initialize` 函数首先执行一些必要的检查,接着会设置类型的大小并创建该类型,设置类型实例的大小,为实例化设备做准备, +`type_initialize` 函数首先执行一些必要的检查,接着会设置类型的大小并创建该类型,设置类型实例的大小,为实例化设备做准备, 然后初始化父设备类型和接口类型,最后调用父设备类型的 `class_base_init` 和自己的 `class_init` 完成初始化工作。 此外,通过上述代码我们还可以得出两点结论: @@ -318,7 +318,7 @@ static void type_initialize(TypeImpl *ti) ## 设备实例化 -完成了设备类型的初始化,接着就要实例化设备了,这个过程由 `object_initialize` 函数实现,而 `object_initialize` 又通过调用 `object_initialize_with_type` 完成实例化工作。抽象地看,设备实例化主要完成三件事:首先,建立`Object` 和 `ObjectClass` 之间的关联;然后,递归调用父类型的 `instance_init` 函数;最后,调用自己的 `instance_init` 函数。需要注意的是,这种抽象的视角简化了设备实例化流程,也模糊了很多真实场景下的具体细节。 +完成了设备类型的初始化,接着就要实例化设备了,这个过程由 `object_initialize` 函数实现,而 `object_initialize` 又通过调用 `object_initialize_with_type` 完成实例化工作。抽象地看,设备实例化主要完成三件事:首先,建立 `Object` 和 `ObjectClass` 之间的关联;然后,递归调用父类型的 `instance_init` 函数;最后,调用自己的 `instance_init` 函数。需要注意的是,这种抽象的视角简化了设备实例化流程,也模糊了很多真实场景下的具体细节。 ### 实例化函数 @@ -326,7 +326,7 @@ static void type_initialize(TypeImpl *ti) ![](images/qemu-system-device-model-part1/DeviceClass.svg) -这里我们需要关注的是负责实例化 `DeviceClass` 的函数 ` device_initfn`: +这里我们需要关注的是负责实例化 `DeviceClass` 的函数 `device_initfn`: ```c /* hw/core/qdev.c: 652 */ @@ -349,16 +349,16 @@ static void device_initfn(Object *obj) } ``` -` device_initfn` 函数的整体执行逻辑比较简单,主要分为以下四个步骤: +`device_initfn` 函数的整体执行逻辑比较简单,主要分为以下四个步骤: - **设备状态转换:** 将传入的 `Object` 指针转换为 `DeviceState` 指针。`DEVICE` 是一个宏,用于从基类 `Object` 转换到派生类 `DeviceState` - **检查机器准备状态:** 使用 `phase_check` 函数检查当前的机器初始化阶段是否为 `PHASE_MACHINE_READY`。如果是,则设置设备的 `hotplugged` 属性为 1,表示该设备是在虚拟机已经启动后热插入的。同时,全局变量 `qdev_hot_added` 被设置为 `true`,表示有设备被热插入 -- **初始化设备属性:** `instance_id_alias` 被设置为 `-1`,表示没有别名;`realized` 被设置为 `false`,表示设备尚未实现;`allow_unplug_during_migration` 被设置为 `false`,表示在迁移期间不允许拔出此设备 +- **初始化设备属性:**`instance_id_alias` 被设置为 `-1`,表示没有别名;`realized` 被设置为 `false`,表示设备尚未实现;`allow_unplug_during_migration` 被设置为 `false`,表示在迁移期间不允许拔出此设备 - **初始化设备的 GPIO 和时钟列表:** 使用 `QLIST_INIT` 宏初始化了设备的 GPIO 和时钟列表 ### realized 属性设置 -我们注意到 ` device_initfn` 函数将 `DeviceState` 的 `realized` 被设置为 `false`,意味着这个设备还没有被实现,那么这个属性究竟是什么时候被设置为 `Ture` 的呢?通过分析 QEMU 的启动流程,可以梳理出如下函数调用链: +我们注意到 `device_initfn` 函数将 `DeviceState` 的 `realized` 被设置为 `false`,意味着这个设备还没有被实现,那么这个属性究竟是什么时候被设置为 `Ture` 的呢?通过分析 QEMU 的启动流程,可以梳理出如下函数调用链: ![](images/qemu-system-device-model-part1/qdev_realize.svg) -- Gitee From 701fb034ee66877bf46d05b09e816102890fa633 Mon Sep 17 00:00:00 2001 From: jl-jiang Date: Sat, 26 Aug 2023 17:37:33 +0800 Subject: [PATCH 04/13] qemu-system-device-model-part1.md: commit correct result of tinycorrect-images Signed-off-by: jl-jiang --- articles/20230815-qemu-system-device-model-part1.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index 43243b0..046d192 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -1,4 +1,4 @@ -> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces codeinline]
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces codeinline images]
> Author: jl-jiang
> Date: 2023/08/15
> Revisor: Bin Meng
@@ -182,7 +182,7 @@ void register_module_init(void (*fn)(void), module_init_type type) QEMU 维护了一个全局的链表头数组 `init_type_list`,分别指向不同类型的注册函数链表,具体如下图所示: -![](images/qemu-system-device-model-part1/init_type_list.svg) +![init_type_list.svg](images/qemu-system-device-model-part1/init_type_list.svg) `register_module_init` 函数会将待注册的设备类型注册函数,这里的例子中是 `e1000_register_types`,添加到 `MODULE_INIT_QOM` 下标所指向的链表尾部,完成注册工作。直到此时,真正的设备类型注册函数都没有执行,到底什么时候进行设备类型的注册呢?在[之前的文章](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230801-qemu-system-event-loop-part2.md)中,我们有分析过 QEMU 主事件循环的一般执行流程,`main` 函数会调用 `qemu_init` 函数进行初始化,`qemu_init` 在执行过程中会调用 `qemu_init_subsystems` 函数,而 `qemu_init_subsystems` 则会调用 `module_call_init` 函数,并传入参数 `MODULE_INIT_QOM`: @@ -324,7 +324,7 @@ static void type_initialize(TypeImpl *ti) 下面以 `DeviceClass` 为例详细分析设备的实例化过程。`DeviceClass` 是 QEMU 设备模型的核心组件,它继承自 `ObjectClass`,提供了设备的基本行为和属性的模板,是很多设备的父类,其实例化过程很有代表性。下图给出了 `DeviceClass` 有关类和对象之间的关系: -![](images/qemu-system-device-model-part1/DeviceClass.svg) +![DeviceClass.svg](images/qemu-system-device-model-part1/DeviceClass.svg) 这里我们需要关注的是负责实例化 `DeviceClass` 的函数 `device_initfn`: @@ -360,7 +360,7 @@ static void device_initfn(Object *obj) 我们注意到 `device_initfn` 函数将 `DeviceState` 的 `realized` 被设置为 `false`,意味着这个设备还没有被实现,那么这个属性究竟是什么时候被设置为 `Ture` 的呢?通过分析 QEMU 的启动流程,可以梳理出如下函数调用链: -![](images/qemu-system-device-model-part1/qdev_realize.svg) +![qdev_realize.svg](images/qemu-system-device-model-part1/qdev_realize.svg) QEMU 在初始化阶段经过一系列函数调用,最终调用 `qdev_realize` 函数,该函数是 QEMU 设备模型中的一个核心函数,用于激活设备,也就是将设备对应的 `DeviceState` 结构的 `realized` 属性设置为 `true`,表示设备已经被实例化并准备好在模拟环境中运行。在设置 `realized` 属性之前,函数会执行一些基础的检查,以确保设备尚未被实例化并且没有父总线,然后函数会检查设备总线类型并设置父总线,最后调用 `object_property_set_bool` 函数设置 `realized` 属性设置为 `true`,完成设备实例化。 -- Gitee From c1ad1d73b9bfae08447a09146549021eb0e2d4e8 Mon Sep 17 00:00:00 2001 From: jl-jiang Date: Sat, 26 Aug 2023 17:38:27 +0800 Subject: [PATCH 05/13] qemu-system-device-model-part1.md: commit correct result of tinycorrect-pangu Signed-off-by: jl-jiang --- articles/20230815-qemu-system-device-model-part1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index 046d192..59c0732 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -1,4 +1,4 @@ -> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces codeinline images]
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces codeinline images urls refs pangu]
> Author: jl-jiang
> Date: 2023/08/15
> Revisor: Bin Meng
@@ -382,6 +382,6 @@ QEMU 在初始化阶段经过一系列函数调用,最终调用 `qdev_realize` ## 参考资料 - [The QEMU Object Model (QOM) ](https://www.qemu.org/docs/master/devel/qom.html) -- [浅谈QEMU的对象系统](https://juejin.cn/post/6844903845550620685) +- [浅谈 QEMU 的对象系统](https://juejin.cn/post/6844903845550620685) - [understanding_qemu](https://richardweiyang-2.gitbook.io/understanding_qemu) - 《QEMU/KVM 源码解析与应用》李强,机械工业出版社 -- Gitee From 1440bf292f84bda8bdc68b355243b0a5517a1200 Mon Sep 17 00:00:00 2001 From: jl-jiang Date: Sat, 26 Aug 2023 17:38:53 +0800 Subject: [PATCH 06/13] qemu-system-device-model-part1.md: commit correct result of tinycorrect-autocorrect Signed-off-by: jl-jiang --- articles/20230815-qemu-system-device-model-part1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index 59c0732..4cb9904 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -1,4 +1,4 @@ -> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces codeinline images urls refs pangu]
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces codeinline images urls refs pangu autocorrect]
> Author: jl-jiang
> Date: 2023/08/15
> Revisor: Bin Meng
@@ -112,7 +112,7 @@ struct TypeImpl 通过观察不难发现 `TypeInfo` 和 `TypeImpl` 两个结构体的结构相似,绝大多数成员变量都相同,那么为什么注册过程中不直接使用,而要重新复制一遍呢?这与 QEMU 对象系统的设计理念有关,`TypeInfo` 结构体是面向 API 使用者的一个工具,使用者只有在注册设备类型的时候会使用到 `TypeInfo`,提供类型注册所需的必要信息和回调函数,此后 QEMU 会根据 `TypeInfo` 自动生成对应的 `TypeImpl` 结构体,至此 `TypeInfo` 的生命周期就结束了。换句话说,`TypeInfo` 保存静态的注册数据,而 `TypeImpl` 保存动态的运行数据,这种设计降低了系统的耦合度,提升了稳定性。 -下面我们来分析 `type_register()` 函数的调用时机,这里就需要引申出另一个问题,就是注册函数本身也有“注册 - 执行”机制。我们以虚拟网卡设备 e1000 为例进行分析: +下面我们来分析 `type_register()` 函数的调用时机,这里就需要引申出另一个问题,就是注册函数本身也有 “注册 - 执行” 机制。我们以虚拟网卡设备 e1000 为例进行分析: ```c /* hw/e1000.c: 1822 */ -- Gitee From 5ece4a7feb104128536385f96e5f1f7f2cdf37a4 Mon Sep 17 00:00:00 2001 From: jl-jiang Date: Sat, 26 Aug 2023 17:40:09 +0800 Subject: [PATCH 07/13] fix typos --- articles/20230815-qemu-system-device-model-part1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index 4cb9904..0a2f78d 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -377,11 +377,11 @@ QEMU 在初始化阶段经过一系列函数调用,最终调用 `qdev_realize` ## 总结 -本文从设备的生命周期这一角度入手,介绍了设备类型注册、设备类型初始化、设备实例化等环节的基本原理和执行流程,打通了从设备类型表创建到设备销毁的逻辑链条,在一个较为抽象的层面对 QEMU 设备模型进行了初步分析。下一篇文章中,我们将专题介绍 QEMU 对象模型以及面向对象的设备管理。 +本文从设备的生命周期这一角度入手,介绍了设备类型注册、设备类型初始化、设备实例化等环节的基本原理和执行流程,打通了从设备类型表创建到设备销毁的逻辑链条,在一个较为抽象的层面对 QEMU 设备模型进行了初步分析。下一篇文章中,我们将专题介绍 QEMU 对象模型以及面向对象的设备管理方法。 ## 参考资料 - [The QEMU Object Model (QOM) ](https://www.qemu.org/docs/master/devel/qom.html) -- [浅谈 QEMU 的对象系统](https://juejin.cn/post/6844903845550620685) - [understanding_qemu](https://richardweiyang-2.gitbook.io/understanding_qemu) +- [浅谈 QEMU 的对象系统](https://juejin.cn/post/6844903845550620685) - 《QEMU/KVM 源码解析与应用》李强,机械工业出版社 -- Gitee From d2a04aa3e606dedb8e26006c5904222c550a5553 Mon Sep 17 00:00:00 2001 From: jl-jiang Date: Sat, 26 Aug 2023 17:41:15 +0800 Subject: [PATCH 08/13] qemu-system-device-model-part1.md: commit correct result of tinycorrect-urls Signed-off-by: jl-jiang --- .../20230815-qemu-system-device-model-part1.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index 0a2f78d..c5161e9 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -1,4 +1,4 @@ -> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [spaces codeinline images urls refs pangu autocorrect]
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [urls]
> Author: jl-jiang
> Date: 2023/08/15
> Revisor: Bin Meng
@@ -184,7 +184,7 @@ QEMU 维护了一个全局的链表头数组 `init_type_list`,分别指向不 ![init_type_list.svg](images/qemu-system-device-model-part1/init_type_list.svg) - `register_module_init` 函数会将待注册的设备类型注册函数,这里的例子中是 `e1000_register_types`,添加到 `MODULE_INIT_QOM` 下标所指向的链表尾部,完成注册工作。直到此时,真正的设备类型注册函数都没有执行,到底什么时候进行设备类型的注册呢?在[之前的文章](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230801-qemu-system-event-loop-part2.md)中,我们有分析过 QEMU 主事件循环的一般执行流程,`main` 函数会调用 `qemu_init` 函数进行初始化,`qemu_init` 在执行过程中会调用 `qemu_init_subsystems` 函数,而 `qemu_init_subsystems` 则会调用 `module_call_init` 函数,并传入参数 `MODULE_INIT_QOM`: + `register_module_init` 函数会将待注册的设备类型注册函数,这里的例子中是 `e1000_register_types`,添加到 `MODULE_INIT_QOM` 下标所指向的链表尾部,完成注册工作。直到此时,真正的设备类型注册函数都没有执行,到底什么时候进行设备类型的注册呢?在[之前的文章][001]中,我们有分析过 QEMU 主事件循环的一般执行流程,`main` 函数会调用 `qemu_init` 函数进行初始化,`qemu_init` 在执行过程中会调用 `qemu_init_subsystems` 函数,而 `qemu_init_subsystems` 则会调用 `module_call_init` 函数,并传入参数 `MODULE_INIT_QOM`: ```c /* util/module.c: 755 */ @@ -381,7 +381,12 @@ QEMU 在初始化阶段经过一系列函数调用,最终调用 `qdev_realize` ## 参考资料 -- [The QEMU Object Model (QOM) ](https://www.qemu.org/docs/master/devel/qom.html) -- [understanding_qemu](https://richardweiyang-2.gitbook.io/understanding_qemu) -- [浅谈 QEMU 的对象系统](https://juejin.cn/post/6844903845550620685) +- [The QEMU Object Model (QOM)][004] +- [understanding_qemu][003] +- [浅谈 QEMU 的对象系统][002] - 《QEMU/KVM 源码解析与应用》李强,机械工业出版社 + +[001]: https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230801-qemu-system-event-loop-part2.md +[002]: https://juejin.cn/post/6844903845550620685 +[003]: https://richardweiyang-2.gitbook.io/understanding_qemu +[004]: https://www.qemu.org/docs/master/devel/qom.html -- Gitee From bd4a6a0a88448082cd962d8edd6972dd0cbcd718 Mon Sep 17 00:00:00 2001 From: falcon Date: Sat, 2 Sep 2023 11:46:35 +0000 Subject: [PATCH 09/13] Update articles/20230815-qemu-system-device-model-part1.md --- articles/20230815-qemu-system-device-model-part1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index c5161e9..81bcdcc 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -208,7 +208,7 @@ void module_call_init(module_init_type type) } ``` -这里 `module_call_init` 函数作用就是找到 `MODULE_INIT_QOM` 对应的链表,然后依次执行链表项中的 `init` 成员函数,对于 e1000 网卡而言就是之前通过 `register_module_init` 注册的的 `e1000_register_types` 函数。 +这里 `module_call_init` 函数作用就是找到 `MODULE_INIT_QOM` 对应的链表,然后依次执行链表项中的 `init` 成员函数,对于 e1000 网卡而言就是之前通过 `register_module_init` 注册的 `e1000_register_types` 函数。 到此为止,设备类型注册的逻辑链条就算完整了。 -- Gitee From 375a725ab5ba168b319d224271e1a7a681489770 Mon Sep 17 00:00:00 2001 From: falcon Date: Sat, 2 Sep 2023 11:47:02 +0000 Subject: [PATCH 10/13] Update articles/20230815-qemu-system-device-model-part1.md --- articles/20230815-qemu-system-device-model-part1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index 81bcdcc..97e34fc 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -368,7 +368,7 @@ QEMU 在初始化阶段经过一系列函数调用,最终调用 `qdev_realize` 设备销毁的步骤与设备创建过程正好相反,一般包含以下四个步骤: -- **设备反实例化:** 停止操作设备并释放占用资源后,通过代码 `object_property_set_bool(OBJECT(dev), "realized", false, &error)` 设置 `realized` 属性设置为 `false`,标记设备已经反实例化 +- **设备反实例化:** 停止操作设备并释放占用资源后,通过代码 `object_property_set_bool(OBJECT(dev), "realized", false, &error)` 把 `realized` 属性设置为 `false`,标记设备已经反实例化 - **从总线中移除:** 设备通常连接到一个总线,在销毁设备之前,需要从其父总线中将之移除 - **销毁设备对象:** 调用 `object_unref` 函数减少设备对象的引用计数,当引用计数降为 0 时,并调用设备对象的析构函数,释放所占内存空间及其他资源 - **反注册设备类型:** 有时可能还需要反注册设备类型,注销回调函数,清理释放为设备分配的各种资源 -- Gitee From fe3530a373cc559bd4e25cfca78303816258383a Mon Sep 17 00:00:00 2001 From: falcon Date: Sat, 2 Sep 2023 11:47:22 +0000 Subject: [PATCH 11/13] Update articles/20230815-qemu-system-device-model-part1.md --- articles/20230815-qemu-system-device-model-part1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index 97e34fc..1d538ca 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -369,7 +369,7 @@ QEMU 在初始化阶段经过一系列函数调用,最终调用 `qdev_realize` 设备销毁的步骤与设备创建过程正好相反,一般包含以下四个步骤: - **设备反实例化:** 停止操作设备并释放占用资源后,通过代码 `object_property_set_bool(OBJECT(dev), "realized", false, &error)` 把 `realized` 属性设置为 `false`,标记设备已经反实例化 -- **从总线中移除:** 设备通常连接到一个总线,在销毁设备之前,需要从其父总线中将之移除 +- **从总线中移除:** 设备通常连接到一个总线,在销毁设备之前,需要将其从父总线中移除 - **销毁设备对象:** 调用 `object_unref` 函数减少设备对象的引用计数,当引用计数降为 0 时,并调用设备对象的析构函数,释放所占内存空间及其他资源 - **反注册设备类型:** 有时可能还需要反注册设备类型,注销回调函数,清理释放为设备分配的各种资源 -- Gitee From 08e833fcb8216830212fdb17a17015991b3cf3f5 Mon Sep 17 00:00:00 2001 From: falcon Date: Sat, 2 Sep 2023 11:47:51 +0000 Subject: [PATCH 12/13] Update articles/20230815-qemu-system-device-model-part1.md --- articles/20230815-qemu-system-device-model-part1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index 1d538ca..8e6a39b 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -358,7 +358,7 @@ static void device_initfn(Object *obj) ### realized 属性设置 -我们注意到 `device_initfn` 函数将 `DeviceState` 的 `realized` 被设置为 `false`,意味着这个设备还没有被实现,那么这个属性究竟是什么时候被设置为 `Ture` 的呢?通过分析 QEMU 的启动流程,可以梳理出如下函数调用链: +我们注意到 `device_initfn` 函数将 `DeviceState` 的 `realized` 设置为 `false`,意味着这个设备还没有被实现,那么这个属性究竟是什么时候被设置为 `true` 的呢?通过分析 QEMU 的启动流程,可以梳理出如下函数调用链: ![qdev_realize.svg](images/qemu-system-device-model-part1/qdev_realize.svg) -- Gitee From ed4a040c30f58414ad6a2a7669abd609ba0cca46 Mon Sep 17 00:00:00 2001 From: falcon Date: Sat, 2 Sep 2023 11:48:26 +0000 Subject: [PATCH 13/13] Update articles/20230815-qemu-system-device-model-part1.md --- articles/20230815-qemu-system-device-model-part1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/articles/20230815-qemu-system-device-model-part1.md b/articles/20230815-qemu-system-device-model-part1.md index 8e6a39b..2114c79 100644 --- a/articles/20230815-qemu-system-device-model-part1.md +++ b/articles/20230815-qemu-system-device-model-part1.md @@ -362,7 +362,7 @@ static void device_initfn(Object *obj) ![qdev_realize.svg](images/qemu-system-device-model-part1/qdev_realize.svg) -QEMU 在初始化阶段经过一系列函数调用,最终调用 `qdev_realize` 函数,该函数是 QEMU 设备模型中的一个核心函数,用于激活设备,也就是将设备对应的 `DeviceState` 结构的 `realized` 属性设置为 `true`,表示设备已经被实例化并准备好在模拟环境中运行。在设置 `realized` 属性之前,函数会执行一些基础的检查,以确保设备尚未被实例化并且没有父总线,然后函数会检查设备总线类型并设置父总线,最后调用 `object_property_set_bool` 函数设置 `realized` 属性设置为 `true`,完成设备实例化。 +QEMU 在初始化阶段经过一系列函数调用,最终调用 `qdev_realize` 函数,该函数是 QEMU 设备模型中的一个核心函数,用于激活设备,也就是将设备对应的 `DeviceState` 结构的 `realized` 属性设置为 `true`,表示设备已经被实例化并准备好在模拟环境中运行。在设置 `realized` 属性之前,函数会执行一些基础的检查,以确保设备尚未被实例化并且没有父总线,然后函数会检查设备总线类型并设置父总线,最后调用 `object_property_set_bool` 函数设置 `realized` 属性为 `true`,完成设备实例化。 ## 设备销毁 -- Gitee