diff --git a/articles/20230830-qemu-system-device-model-part3.md b/articles/20230830-qemu-system-device-model-part3.md
new file mode 100644
index 0000000000000000000000000000000000000000..489fb965a608b786148236282534df01302d5d9d
--- /dev/null
+++ b/articles/20230830-qemu-system-device-model-part3.md
@@ -0,0 +1,304 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [urls refs autocorrect]
+> Author: jl-jiang
+> Date: 2023/08/30
+> 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 设备模型简析(三):QOM 设计实现
+
+## 前言
+
+在 [上一篇文章][002] 中我们聚焦 QEMU 中面向对象的设备管理机制,阐述了引入对象模型的必要性,介绍了 QOM 基本功能和顶层设计,本文将在此基础上进一步深入,详细分析 QOM 对象系统的设计思路以及实现方式。
+
+## 关键结构
+
+通过之前的分析,我们已经知道 QOM 有如下几大关键数据结构:`Object`、`ObjectClass`、`TypeInfo` 和 `TypeImpl`,他们的关系如下图所示:
+
+
+
+通过上图不难看出,QOM 对象模型的核心结构是 `Object`,这个结构体十分简洁,只保存了有关类型和父类的信息。`ObjectClass` 结构体就比较复杂,保存指向类型信息 `TypeImpl` 结构体的指针,保证了能够获取有关类型的信息。`TypeImpl` 存储着一个类型的信息,包括类型名称、类型大小、是否抽象类、父类名称、父类类型指针、`ObjectClass` 等。`TypeInfo` 结构体没有直接与其他任何数据结构产生直接关联,通过前面的分析我们已经知道,这是面向开发者的一个工具结构,主要用于在注册类型的时候提供类型的基本信息,在类型注册伊始,QEMU 会自动生成对应的 `TypeImpl` 结构体,保存类型的全部信息。
+
+## 面向对象特性
+
+首先,我们需要回顾一下面向对象的基本特征:
+
+- 封装
+- 接口
+- 继承
+- 析构
+- 静态成员
+- 多态
+- 动态类型装换
+
+下面我们将详细分析上述特性在 QOM 对象系统中的具体实现。
+
+### 封装
+
+在分析 QOM 如何实现封装之前,我们需要再次回顾 `Object` 结构体的定义:
+
+```c
+/* include/qom/object.h: 153 */
+
+struct Object
+{
+ /* private: */
+ ObjectClass *class;
+ ObjectFree *free;
+ GHashTable *properties;
+ uint32_t ref;
+ Object *parent;
+};
+```
+
+这里需要关注的是第一行的注释,它表示结构体中的所有属性都是私有的,只能被类的内部成员访问和修改。但是,仅靠 C 语言中的结构体是无法实现对私有变量的访问控制的,因此 QEMU 在 `Object` 中引入了属性表,即 `properties` 指针,它指向一张哈希表,该表含了 `Object` 中的所有可以访问、修改的数据和函数其中每一个键值对表示 `property` 的名称以及指向相应 `ObjectProperty` 结构体的指针。下面给出 `ObjectProperty` 结构体的实现代码:
+
+```c
+/* include/qom/object.h: 88 */
+
+struct ObjectProperty
+{
+ char *name;
+ char *type;
+ char *description;
+ ObjectPropertyAccessor *get;
+ ObjectPropertyAccessor *set;
+ ObjectPropertyResolve *resolve;
+ ObjectPropertyRelease *release;
+ ObjectPropertyInit *init;
+ void *opaque;
+ QObject *defval;
+};
+```
+
+可以看到,`ObjectProperty` 结构体包含了这个属性的名称、类型、描述、读写方法以及解析和释放函数,还包括这个 `property` 特有的属性,使用 `opaque` 指针来表示。QOM 通过 `ObjectProperty` 结构体将对象的每个数据都保存在这样一个单元之中,再利用一个哈希表实现对对象所有数据的统一管理,进而实现了数据封装。当用户需要向 `Object` 中增加属性时,需要调用 `object_property_add` 函数:
+
+```c
+/* qom/object.c: 1257 */
+
+ObjectProperty *
+object_property_add(Object *obj, const char *name, const char *type,
+ ObjectPropertyAccessor *get,
+ ObjectPropertyAccessor *set,
+ ObjectPropertyRelease *release,
+ void *opaque)
+{
+ return object_property_try_add(obj, name, type, get, set, release,
+ opaque, &error_abort);
+}
+```
+
+该函数通过进一步调用 `object_property_try_add` 函数向 `properties` 所指向的哈希表中插入了一个新的属性:
+
+```c
+/* qom/object.c: 1206 */
+
+ObjectProperty *
+object_property_try_add(Object *obj, const char *name, const char *type,
+ ObjectPropertyAccessor *get,
+ ObjectPropertyAccessor *set,
+ ObjectPropertyRelease *release,
+ void *opaque, Error **errp)
+{
+ ObjectProperty *prop;
+ size_t name_len = strlen(name);
+
+ if (name_len >= 3 && !memcmp(name + name_len - 3, "[*]", 4)) {
+ int i;
+ ObjectProperty *ret = NULL;
+ char *name_no_array = g_strdup(name);
+
+ name_no_array[name_len - 3] = '\0';
+ for (i = 0; i < INT16_MAX; ++i) {
+ char *full_name = g_strdup_printf("%s[%d]", name_no_array, i);
+
+ ret = object_property_try_add(obj, full_name, type, get, set,
+ release, opaque, NULL);
+ g_free(full_name);
+ if (ret) {
+ break;
+ }
+ }
+ g_free(name_no_array);
+ assert(ret);
+ return ret;
+ }
+
+ if (object_property_find(obj, name) != NULL) {
+ error_setg(errp, "attempt to add duplicate property '%s' to object (type '%s')",
+ name, object_get_typename(obj));
+ 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(obj->properties, prop->name, prop);
+ return prop;
+}
+```
+
+### 继承
+
+在面向对象编程中主要包括三种继承形式:
+
+- **可视继承:** QEMU 中可视继承主要用于处理图形界面的相关问题,这里不做深入讨论
+- **实现继承:** 子类能够直接使用基类的属性和方法而无需重新编写代码
+- **接口继承:** 子类仅使用基类的属性和方法名称,属性和代码的具体内容需要重新编写代码实现
+
+对于实现继承,QOM 通过结构体的包含关系完成。在 QEMU 中我们创建一个新类时,会实现两个数据结构:类的数据结构 `ObjectClass` 和对象的数据结构 `Object`,由于这两个结构体中的第一个成员变量就是父类(对象),那么只要通过 “指针 + 偏移量” 就可以直接使用父类的属性和方法,完成了实现继承的功能。
+
+对于接口继承,QEMU 中定义了专门的接口结构:
+
+```c
+/* include/qom/object.h: 514 */
+
+struct InterfaceClass
+{
+ ObjectClass parent_class;
+ /* private: */
+ ObjectClass *concrete_class;
+ Type interface_type;
+};
+```
+
+在 QOM 中一个类可以实现多个接口,也就是接口继承。`ObjectClass` 结构体中与接口继承相关的属性是 `interfaces`,它指向一条链表,链表中的每个元素都是一个指向 `InterfaceClass` 的指针,再通过其中的 `interface_type` 指针指向一个 `TypeImpl` 结构体,我们可以通过给该指针指向的 `TypeImpl` 结构体中的函数指针赋值,从而达到实现对应接口的目的。
+
+### 多态
+
+多态是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。为了实现多态,QOM 实现了一个非常重要的功能,即动态强制类型转换(dynamic cast)。我们可以使用相关的函数,将一个 `Object` 的指针在运行时 cast 为子类对象的指针,可以将一个 `ObjectClass` 的指针在运行时 cast 为子类的指针。这样就可以调用子类中定义的函数指针来完成相应的功能。动态 cast 功能主要由 `object_class_dynamic_cast` 函数实现:
+
+```c
+/* qom/object.c: 908 */
+
+ObjectClass *object_class_dynamic_cast(ObjectClass *class,
+ const char *typename)
+{
+ ObjectClass *ret = NULL;
+ TypeImpl *target_type;
+ TypeImpl *type;
+
+ if (!class) {
+ return NULL;
+ }
+
+ /* A simple fast path that can trigger a lot for leaf classes. */
+ type = class->type;
+ if (type->name == typename) {
+ return class;
+ }
+
+ target_type = type_get_by_name(typename);
+ if (!target_type) {
+ /* target class type unknown, so fail the cast */
+ return NULL;
+ }
+
+ if (type->class->interfaces &&
+ type_is_ancestor(target_type, type_interface)) {
+ int found = 0;
+ GSList *i;
+
+ for (i = class->interfaces; i; i = i->next) {
+ ObjectClass *target_class = i->data;
+
+ if (type_is_ancestor(target_class->type, target_type)) {
+ ret = target_class;
+ found++;
+ }
+ }
+
+ /* The match was ambiguous, don't allow a cast */
+ if (found > 1) {
+ ret = NULL;
+ }
+ } else if (type_is_ancestor(type, target_type)) {
+ ret = class;
+ }
+
+ return ret;
+}
+```
+
+### 析构
+
+QOM 的通过 “引用计数” 来判断何时调用析构函数删除对象,`Object` 的结构体中有一个专门用于对 `Object` 引用的计数变量 `ref`,如果 `ref` 的值减少为 0,就意味着系统不会继续使用这个对象了,那么就可以对相应的内存空间等进行回收操作:
+
+```c
+/* qom/object.c: 1192 */
+
+void object_unref(void *objptr)
+{
+ Object *obj = OBJECT(objptr);
+ if (!obj) {
+ return;
+ }
+ g_assert(obj->ref > 0);
+
+ /* parent always holds a reference to its children */
+ if (qatomic_fetch_dec(&obj->ref) == 1) {
+ object_finalize(obj);
+ }
+}
+```
+
+在注册类型时,可以通过定义 `TypeInfo` 结构体中的 `instance_finalize` 实现自定义析构函数,对于引用计数为 0 的 `Object` 进行垃圾回收操作。QOM 提供了默认析构函数:
+
+```c
+/* qom/object.c: 688 */
+
+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);
+ g_assert(obj->parent == NULL);
+ if (obj->free) {
+ obj->free(obj);
+ }
+}
+```
+
+`Object` 数据结构中有一个 `ObjectFree *` 类型的函数指针 `free`,当 `Object` 的引用计数为 0 时,就会调用这个函数进行垃圾回收:
+
+```c
+/* qom/object.c: 677 */
+
+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));
+ }
+}
+```
+
+该函数会调用 `TypeImpl` 中的实例析构函数。如果存在父类,则会递归继续调用父类的实例析构函数。这里之所以需要调用父类实例析构函数是因为一个 `Object` 结构体的第一个成员变量就是父类对象的实例,因此当我们需要对对象析构时,不仅要调用当前类的析构方法,也需要调用父类的析构方法将结构体中的第一个成员进行析构。
+
+## 总结
+
+QOM 实现了一套较为完备的对象管理系统,包括自动化的对象注册、完善的对象管理以及巧妙的动态类型转换,本文梳理了 QOM 实现中几大关键数据结构之间的联系,详细分析了封装、多态、继承以及析构等面向对象特性在 QOM 中的集体实现,与之前的文章相呼应,打通了 QEMU 设备模型从使用到实现的全过程逻辑链条。
+
+## 参考资料
+
+- [Kvmforum14-qom.pdf][003]
+- [QOM 实现分析][001]
+
+[001]: http://blog.chinaunix.net/uid-28541347-id-5784376.html
+[002]: https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230822-qemu-system-device-model-part2.md
+[003]: https://www.linux-kvm.org/images/9/90/Kvmforum14-qom.pdf
diff --git a/articles/assets/datastructure.svg b/articles/assets/datastructure.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2c04f83618c68c9afd305c1be1de2d8fd524e189
--- /dev/null
+++ b/articles/assets/datastructure.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/articles/images/qemu-system-device-model-part3/datastructure.svg b/articles/images/qemu-system-device-model-part3/datastructure.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2c04f83618c68c9afd305c1be1de2d8fd524e189
--- /dev/null
+++ b/articles/images/qemu-system-device-model-part3/datastructure.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file