Qmeu 采用了基于事件驱动的架构,全部的事件都在一个事件循环(event loop)中被处理,系统中默认的事件循环是在main-loop.c 中的主循环(main loop)。咱们也可使用 –object iothread,id=my-iothread本身建立事件循环。html
Qemu 中的事件架构来源于glib,其实qemu自己就是基于glib的,qemu中有大量的概念来源于glib,因此在学习qemu以前先了解一下glib有助于更快的理解qemu。下面首先介绍一下glib中的事件机制。数据结构
Glib中是由一个主事件循环(main event loop)来负责处理全部的事件源(source),事件源包括文件描述符(纯文件、管道或者socket)和超时。新的事件源能够经过 g_source_attach()来添加。为了实如今不一样的线程中处理多个、独立的事件源,每个事件源都关联一个主上下文GMainContext的数据结构。一个GMainContex只能在一个线程中运行,但不一样线程中的事件源能够互相添加或删除。GMainContext中的事件源会在GMainContext关联的主事件循环中进行检查和发送(dispatch)。架构
新的事件源类型能够经过包含GSource结构体来建立。新的事件源类型中GSource结构体必须是第一个成员,其余成员放在其后。要建立一个新事件源类型实例,能够调用g_source_new()函数,传入新的事件源类型大小和一个GSourceFuncs类型的变量,这个变量决定了新的事件源类型的控制方式。异步
新的事件源经过两种方式跟主上下文交互。第一种方式是GSourceFuncs中的prepare函数能够设置一个超时时间,来决定主事件循环中轮询的超时时间;第二种方式是经过g_source_add_poll()函数来添加文件描述符。socket
主上下文的一次循环包含四个步骤,分别由四个函数实现:g_main_context_prepare(), g_main_context_query(), g_main_context_check() 和 g_main_context_dispatch(),其状态转换图以下:函数
下面分别简单介绍一下这四个函数的做用:oop
上面就是整个事件的处理流程,咱们须要作的就是把新的source加入到这个处理流程中,glib会负责处理source上注册的各类事件源。Glib中有两个添加函数,分别实现将source 加入到GMaincontext和将fd加入到source的功能:性能
下面介绍一下qemu 是如何使用这一套事件处理流程的。Qemu是基于glib 开发的,继承了不少glib的概念,struct AioContext 就是按照 glib 的source 建立原则新建的一个事件源类型,用来处理信号,中断等事件,其内容以下:学习
struct AioContext {ui
GSource source;
RFifoLock lock;
QLIST_HEAD(, AioHandler) aio_handlers;
int walking_handlers;
uint32_t notify_me;
QemuMutex bh_lock;
struct QEMUBH *first_bh;
int walking_bh;
bool notified;
EventNotifier notifier;
QEMUBH *notify_dummy_bh;
struct ThreadPool *thread_pool;
QEMUTimerListGroup tlg;
int external_disable_cnt;
int epollfd;
bool epoll_enabled;
bool epoll_available;
};
AioContext 拓展了glib 中source的功能,不但支持fd、超时的轮询,还模拟内核中的下半部机制实现了事件的异步通知功能,其中的通知功能是基于 eventfd 实现的。
AioContext 本质上仍是一个 source,咱们在上文中提到,source有一个很重要的成员 GSourceFuncs,它控制着source在主上下文中的控制方式。AioContext 的 GSourceFuncs 定义以下:
static GSourceFuncs aio_source_funcs = {
aio_ctx_prepare,
aio_ctx_check,
aio_ctx_dispatch,
aio_ctx_finalize
};
这几个函数分别在g_main_context_prepare(), g_main_context_check() 和 g_main_context_dispatch() 中被调用。下面分别介绍一下这几个函数的主要功能:
struct QEMUBH {
AioContext *ctx;
QEMUBHFunc *cb;
void *opaque;
QEMUBH *next;
bool scheduled;
bool idle;
bool deleted;
};
2. aio_ctx_check 用来检查若是bh、fd或timer存在就绪,则返回TRUE,从而调用 g_main_context_dispatch()
3. aio_ctx_dispatch 调用aio_dispatch,依次执行就绪的bh、fd和timer,完成依次主循环。
qemu会在初始化的过程当中经过g_source_new 函数把 aio_source_funcs 注册到AioContext。
Qemu中经常使用的 AioContext 实例有四个, qemu_aio_context, iohandler->ctx,iothread 中的AioContext,描述磁盘镜像的BlockDriverState 中的 AioContext。他们负责处理的事件分别是:
Qemu在初始化的过程当中用 g_source_attach 函数把 qemu_aio_context和iohandler->ctx 添加到主循环。
上面是qemu效仿glib 实现的主循环,但主循环存在一些缺陷,好比在主机使用多CPU的状况下伸缩性受到限制,同时主循环使用了qemu全局互斥锁,从而致使vCPU线程和主循环存在锁竞争,致使性能降低。为了解决这个问题,qemu引入了iothread 事件循环,把一些IO操做分配给iothread,从而提升IO性能。
Iothread的建立方式是在qemu启动的时候传入–object iothread,id=my-iothread参数。在iothread线程中循环执行aio_poll,这个函数简化了glib的事件循环,只要存在就绪的fd就执行aio_dispatch,从而执行就绪的bh、fd和timer。
参考: