diff --git a/articles/20230808-qemu-system-event-loop-part3.md b/articles/20230808-qemu-system-event-loop-part3.md
new file mode 100644
index 0000000000000000000000000000000000000000..ffe8094aaa6062489144982d6dbdd6025b2ba9e1
--- /dev/null
+++ b/articles/20230808-qemu-system-event-loop-part3.md
@@ -0,0 +1,301 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [urls autocorrect epw]
+> Author:    jl-jiang 
+> Date:      2023/08/08
+> 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 事件循环机制简析(三):下半部机制
+
+## 前言
+
+在[上一篇文章](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230801-qemu-system-event-loop-part2.md)中,我们详细分析了 QEMU 事件循环机制的基本组成,包括事件源、状态机回调函数以及 poll 机制,介绍了 QEMU 主事件循环的运行流程,本文将在此基础上进一步深入,以 8.0.0 版本的 QEMU RISC-V (qemu-system-riscv64) 为例,分析 QEMU 下半部(Bottom Halves,BH)机制。
+
+在操作系统内核中,下半部是一个延迟执行的函数,在中断上下文之外运行。在 QEMU 中,下半部用于异步事件处理,QEMU 下半部机制利用事件循环机制,向其他模块提供异步调用的接口。因为其它模块的运行可能不在主线程中,但它想把自己注册的函数加入到 QEMU 主线程中运行,这时就可以使用下半部机制。QEMU 下半部功能在一个事件循环中实现,当下半部函数注册到一个事件循环中,这个事件循环在下一次 poll 的时候,就会调用该函数,从而实现异步延迟调用的效果。
+
+## 数据结构
+
+QEMU 将下半部机制所有必要信息(关联事件源、名称、回调函数和状态)进行抽象,封装为 `QEMUBH` 结构体:
+
+```c
+/* util/async.c: 61 */
+
+struct QEMUBH {
+    AioContext *ctx;
+    const char *name;
+    QEMUBHFunc *cb;
+    void *opaque;
+    QSLIST_ENTRY(QEMUBH) next;
+    unsigned flags;
+};
+```
+
+下面逐一分析该结构体各成员变量的含义与作用:
+
+- **AioContext \*ctx:** 指向 `AioContext` 的指针,代表与下半部关联的异步 I/O 上下文。这允许 QEMU 在不同的上下文中处理不同的事件
+- **const char \*name:** 下半部名称
+- **QEMUBHFunc \*cb:** 指向回调函数的指针,当下半部被触发时,这个函数会被调用,这个回调函数通常会处理与下半部关联的事件
+- **void \*opaque:** 指向数据的指针,它可以在回调函数中使用,一般用于为回调函数提供参数或者额外的上下文数据。
+- **QSLIST_ENTRY(QEMUBH) next:** 一个用于将 `QEMUBH` 结构体插入到单向链表的宏
+- **unsigned flags:** 用于存储底部半部的状态和属性的标志位,表示底部半部是否已经被调度或是否已经被删除
+
+其中,`flags` 标志一共有五种:
+
+```c
+/* util/async.c: 44 */
+
+enum {
+    /* Already enqueued and waiting for aio_bh_poll() */
+    BH_PENDING   = (1 << 0),
+
+    /* Invoke the callback */
+    BH_SCHEDULED = (1 << 1),
+
+    /* Delete without invoking callback */
+    BH_DELETED   = (1 << 2),
+
+    /* Delete after invoking callback */
+    BH_ONESHOT   = (1 << 3),
+
+    /* Schedule periodically when the event loop is idle */
+    BH_IDLE      = (1 << 4),
+};
+```
+
+上述五种标志分别对应如下含义:
+
+- **BH_PENDING:** 表示下半部已经入队并等待 `aio_bh_poll()` 的处理,当一个下半部被调度但尚未执行时,设置为该状态
+- **BH_SCHEDULED:** 表示下半部的回调函数应该被调用,当下半部被调度并准备执行其回调函数时,设置为该状态
+- **BH_DELETED:** 表示下半部应该被删除,但在删除之前不应该调用其回调函数,这通常在下半部不再需要时使用以确保其回调函数不被执行
+- **BH_ONESHOT:** 表示下半部应该在调用其回调函数后被删除,一般用于标示只需要执行一次的下半部
+- **BH_IDLE:** 表示下半部应该在事件循环空闲时被周期性地调度,一般用于标示需要在没有其他活动时执行的任务很有用
+
+为什么需要设置 `flag` 标志?因为 QEMU 下半部的注册和执行是异步的,因此需要有一种方法用来通知执行者下半部应该怎样执行。`flag` 标志的设置提供了一种方式来控制和管理下半部的行为。例如,可以组合使用这五种标志来确定是否应该执行下半部的回调函数,或者下半部是否应该在执行后被删除。
+
+## 操作
+
+### 挂载
+
+QEMU 下半部在一个事件循环中实现,因此新建下半部需要指出基于哪个 `AioContext`,同时要指定下半部中需要执行什么函数以及函数参数,下半部的创建由 `aio_bh_new_full` 函数具体执行:
+
+```c
+/* util/async.c: 139 */
+
+QEMUBH *aio_bh_new_full(AioContext *ctx, QEMUBHFunc *cb, void *opaque,
+                        const char *name)
+{
+    QEMUBH *bh;
+    bh = g_new(QEMUBH, 1);
+    *bh = (QEMUBH){
+        .ctx = ctx,
+        .cb = cb,
+        .opaque = opaque,
+        .name = name,
+    };
+    return bh;
+}
+```
+
+下半部创建后需要挂载到相应 `AioContext` 的下半部列表中,该操作由 `aio_bh_enqueue` 完成:
+
+```c
+/* util/async.c: 71 */
+
+static void aio_bh_enqueue(QEMUBH *bh, unsigned new_flags)
+{
+    AioContext *ctx = bh->ctx;
+    unsigned old_flags;
+
+    /*
+     * Synchronizes with atomic_fetch_and() in aio_bh_dequeue(), ensuring that
+     * insertion starts after BH_PENDING is set.
+     */
+    old_flags = qatomic_fetch_or(&bh->flags, BH_PENDING | new_flags);
+
+    if (!(old_flags & BH_PENDING)) {
+        /*
+         * At this point the bottom half becomes visible to aio_bh_poll().
+         * This insertion thus synchronizes with QSLIST_MOVE_ATOMIC in
+         * aio_bh_poll(), ensuring that:
+         * 1. any writes needed by the callback are visible from the callback
+         *    after aio_bh_dequeue() returns bh.
+         * 2. ctx is loaded before the callback has a chance to execute and bh
+         *    could be freed.
+         */
+        QSLIST_INSERT_HEAD_ATOMIC(&ctx->bh_list, bh, next);
+    }
+
+    aio_notify(ctx);
+    /*
+     * Workaround for record/replay.
+     * vCPU execution should be suspended when new BH is set.
+     * This is needed to avoid guest timeouts caused
+     * by the long cycles of the execution.
+     */
+    icount_notify_exit();
+}
+```
+
+一个 `AioContext` 上可以有多个下半部挂载,下半部通过 `AioContext` 的 `bh_list` 成员以及自身数据结构中的 `next` 指针被维护成一个单向链表,如下图所示:
+
+
+
+QEMU 主线程有默认的事件循环 `qemu_aio_context`,将下半部挂载到主线程的事件循环可以调用封装好的内部接口 `qemu_bh_new`:
+
+```c
+/* include/qemu/main-loop.h: 390 */
+
+#define qemu_bh_new(cb, opaque) \
+    qemu_bh_new_full((cb), (opaque), (stringify(cb)))
+```
+
+一般 QEMU 下半部的执行方式是调用 `qemu_bh_schedule` 函数,将 `flag` 标志设置为 `BH_SCHEDULED`,即该下半部应该被计划执行:
+
+```c
+/* util/async.c: 199 */
+
+void qemu_bh_schedule(QEMUBH *bh)
+{
+    aio_bh_enqueue(bh, BH_SCHEDULED);
+}
+```
+
+通过这种方式执行的下半部在执行一次以后不会被删除,因此下一次调度执行不需要重新注册。而通过 `aio_bh_schedule_oneshot_full` 接口创建并执行的下半部会在创建后尽快执行一次,然后就被删除:
+
+```c
+/* util/async.c: 125 */
+
+void aio_bh_schedule_oneshot_full(AioContext *ctx, QEMUBHFunc *cb,
+                                  void *opaque, const char *name)
+{
+    QEMUBH *bh;
+    bh = g_new(QEMUBH, 1);
+    *bh = (QEMUBH){
+        .ctx = ctx,
+        .cb = cb,
+        .opaque = opaque,
+        .name = name,
+    };
+    aio_bh_enqueue(bh, BH_SCHEDULED | BH_ONESHOT);
+}
+```
+
+观察代码可以发现,`aio_bh_schedule_oneshot_full` 和 `qemu_bh_schedule` 函数主要有两处不同:`aio_bh_schedule_oneshot_full` 函数能够创建新的下半部,并且挂载时的 `flag` 标志比 `qemu_bh_schedule` 函数多一个 `BH_ONESHOT`。
+
+### 通知
+
+QEMU 下半部挂载到对应 `AioContext` 上并不会被立即执行,只有当事件循环的执行线程 poll 到 fd 准备好之后,才会触发回调执行下半部,如果对应 `AioContext` 的 fd 一直没有准备就绪,那么 poll 就一直不返回,挂载在上面的下半部就会一直得不到执行。为避免这类问题,QEMU 实现了下半部调度接口,用于通知事件循环调度下半部。
+
+下半部调度接口通过 eventfd 实现,evenfd 可以用于线程间通信。QEMU 在 `aio_context_new` 创建 `AioContext` 时通过调用 `event_notifier_init` 函数初始化 `EventNotifier`,将 `EventNotifier` 中的 `rfd` 设置 `aio_set_event_notifier` 为事件循环要监听的 fd。当一个线程把自己的下半部挂载到 `AioContext` 上后,向 `EventNotifier` 中的 `wfd` 写入内容,另一端监听 `rfd` 的事件循环就会 poll 到其上有事件,然后触发下半部的调度:
+
+```c
+/* util/async.c: 542 */
+
+AioContext *aio_context_new(Error **errp)
+{
+    int ret;
+    AioContext *ctx;
+    ...
+    QSLIST_INIT(&ctx->bh_list);
+	...
+    ret = event_notifier_init(&ctx->notifier, false);
+    ...
+
+    aio_set_event_notifier(ctx, &ctx->notifier,
+                           false,
+                           aio_context_notifier_cb,
+                           aio_context_notifier_poll,
+                           aio_context_notifier_poll_ready);
+	...
+}
+```
+
+### 执行
+
+事件循环在 poll 到 fd 准备好之后,会调度 QEMU 定制的状态机回调函数 `aio_ctx_dispatch`,其主要功能是执行 fd 对应的回调函数,但在此之前会先检查 `AioContext` 上是否挂载有下半部,如果有则会先执行下半部。
+
+`aio_ctx_dispatch` 和 `aio_dispatch` 函数在[上一篇文章](https://gitee.com/tinylab/riscv-linux/blob/master/articles/20230801-qemu-system-event-loop-part2.md)中已经详细分析过,本文不再赘述,这里我们重点关注具体落实下半部调度执行的函数 `aio_bh_poll`:
+
+```c
+/* util/async.c: 159 */
+
+int aio_bh_poll(AioContext *ctx)
+{
+    BHListSlice slice;
+    BHListSlice *s;
+    int ret = 0;
+
+    /* Synchronizes with QSLIST_INSERT_HEAD_ATOMIC in aio_bh_enqueue(). */
+    QSLIST_MOVE_ATOMIC(&slice.bh_list, &ctx->bh_list);
+    QSIMPLEQ_INSERT_TAIL(&ctx->bh_slice_list, &slice, next);
+
+    while ((s = QSIMPLEQ_FIRST(&ctx->bh_slice_list))) {
+        QEMUBH *bh;
+        unsigned flags;
+
+        bh = aio_bh_dequeue(&s->bh_list, &flags);
+        if (!bh) {
+            QSIMPLEQ_REMOVE_HEAD(&ctx->bh_slice_list, next);
+            continue;
+        }
+
+        if ((flags & (BH_SCHEDULED | BH_DELETED)) == BH_SCHEDULED) {
+            /* Idle BHs don't count as progress */
+            if (!(flags & BH_IDLE)) {
+                ret = 1;
+            }
+            aio_bh_call(bh);
+        }
+        if (flags & (BH_DELETED | BH_ONESHOT)) {
+            g_free(bh);
+        }
+    }
+
+    return ret;
+}
+```
+
+`aio_bh_poll` 函数 QEMU 下半部机制的核心函数,这里首先定义了两个 `BHListSlice` 变量和一个返回值 `ret`。`BHListSlice` 是一个结构,它包含一个 `BHList` 类型的列表和一个后继指针 `next`。接着,函数两个宏将当前 `AioContext` 的 `bh_list` 列表中所有下半部移动到一个新的 `BHListSlice` 中,并将这个 `BHListSlice` 添加到 `ctx->bh_slice_list` 的尾部。这样做的目的是保证处理下半部时“先来先处理”的顺序,即新添加的下半部总是在最后得到处理。最后,函数使用 `while` 循环遍历 `bh_slice_list` 中所有的 `BHListSlice`,并更进一步地遍历 `BHListSlice` 中每一个下半部,对于计划执行并且没有被标记删除的下半部,函数会调度执行,对于一次性的或者是被标记删除的下半部,函数会删除它并释放资源。
+
+在此过程中,有关数据结构的关系如下图所示:
+
+
+
+### 卸载
+
+下半部卸载是通过调用 `aio_bh_enqueue` 函数更新 `flag` 标志完成的,设置 `BH_DELETED` 标志能够使下半部在下一次 dispatch 阶段被删掉并释放对应的内存空间:
+
+```c
+/* util/async.c: 214 */
+
+void qemu_bh_delete(QEMUBH *bh)
+{
+    aio_bh_enqueue(bh, BH_DELETED);
+}
+```
+
+### 禁用
+
+当我们希望下半部不再被事件循环调度,但又暂时不想删除这个下半部时可以调用 `qemu_bh_cancel` 函数实现:
+
+```c
+/* util/async.c: 206 */
+
+void qemu_bh_cancel(QEMUBH *bh)
+{
+    qatomic_and(&bh->flags, ~BH_SCHEDULED);
+}
+```
+
+这个函数非常简洁,只有一行代码,使用原子操作来清除 `bh->flags` 中的 `BH_SCHEDULED` 标志。`BH_SCHEDULED` 标志表示这个下半部已经被计划执行,清除这个标志,意味着此后事件循环的 dispatch 时这个下半部不会再被调度执行。
+
+## 总结
+
+本文在前两篇文章的基础上,进一步分析了 QEMU 下半部机制,下半部利用事件循环机制,向其他模块提供异步调用的接口。文章梳理了下半部机制的主要数据结构以及它们之间的关系,介绍了 QEMU 下半部机制的执行原理,同时还整理了常见下半部操作的接口和用法。
+
+## 参考资料
+
+- [深入理解 QEMU 事件循环](https://blog.csdn.net/huang987246510/article/details/100110183)
+- [QEMU Internals: Event loops](https://gitee.com/link?target=http%3A%2F%2Fblog.vmsplice.net%2F2020%2F08%2Fqemu-internals-event-loops.html)
+- 《QEMU/KVM 源码解析与应用》李强,机械工业出版社
diff --git a/articles/images/qemu-system-event-loop-part3/BHList.svg b/articles/images/qemu-system-event-loop-part3/BHList.svg
new file mode 100644
index 0000000000000000000000000000000000000000..586f58d234cb10ed450970404afa7d2c3b2a967c
--- /dev/null
+++ b/articles/images/qemu-system-event-loop-part3/BHList.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/articles/images/qemu-system-event-loop-part3/QEMUBH.svg b/articles/images/qemu-system-event-loop-part3/QEMUBH.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6e5f69f5eb5a0c19e0c3524552a964d7417827f5
--- /dev/null
+++ b/articles/images/qemu-system-event-loop-part3/QEMUBH.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file