# qemu事件框架
qemu事件框架基于Glib事件循环
简述Glib事件循环:
Glib提供了一套事件分发接口,在这套接口上注册事件源以及对应的回调函数,在条件满足时触发回调。Glib的核心为poll机制,通过poll查看事件源是否满足触发条件,满足则调用相应函数。
Glib管理的事件源可以在一个线程中处理,也可不同线程,取决于事件源所在上下文,一个线程可有多个上下文,一个上下文只能在一个线程
qemu基于Glib但并不是全套照搬,其中将循环入口函数改为while进行循环。
[Glib事件循环状态机]
如上图所示,Glib事件循环分为上图四个部分:初始化、准备、poll、调度。
使用者需要在这四个接口处设置对应的处理函数
prepare: gboolean (*prepare) (GSource *source, gint *timeout_);
Glib初始化完成后会调用此接口,此接口返回TRUE表示事件源都已准备好,告诉Glib跳过poll直接检查判断是否执行对应回调。返回FALSE表示需要poll机制监听事件源是否准备好,如果有事件源没准备好,通过参数timeout指定poll最长的阻塞时间,超时后直接返回,超时接口可以防止一个fd或者其它事件源阻塞整个应用
query:gint (g_main_context_query) (GMainContext *context, gint max_priority, gint *timeout_, GPollFD *fds, gint n_fds);
Glib在prepare完成之后,可以通过query查询一个上下文将要poll的所有事件,这个接口需要用户主动调用
check:gboolean (*check) (GSource *source);
Glib在poll返回后会调用此接口,用户通过注册此接口判断哪些事件源需要被处理,此接口返回TRUE表示对应事件源的回调函数需要被执行,返回FALSE表示不需要被执行
dispatch:gboolean (*dispatch) (GSource *source, GSourceFunc callback, gpointer user_data);
Glib根据check的结果调用此接口,参数callback和user_data是用户通过g_source_set_callback注册的事件源回调和对应的参数,用户可以在dispatch中选择直接执行callback,也可以不执行
qemu事件循环初始化
在qemu_init_main_loop中进行
```
static void
qemu_init_main_loop()
{
GSource *src;
qemu_aio_context = aio_context_new(); // 创建qemu定制的事件源qemu_aio_context
gpollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD));
src = aio_get_g_source(qemu_aio_context); // 从定制事件源中获取Glib原始的事件源
g_source_set_name(src, "aio-context"); // 设置事件源名称
g_source_attach(src, NULL); // 将事件源添加到Glib默认事件循环上下文
g_source_unref(src);
src = iohandler_get_g_source(); // 获取另一个定制的事件源 iohandler_ctx
g_source_set_name(src, "io-handler");
g_source_attach(src, NULL); // 将事件源添加到Glib默认事件循环上下文
g_source_unref(src);
}
```
qemu中事件源应以Glib提供事件源GSource作为第一成员:
```
struct AioContext {
GSource source; //描述符监听
struct QEMUBH *first_bh; //用于实现下半部
EventNotifier notifier; //用于事件通知
QEMUTimerListGroup tlg; //用于时钟事件源监听
}
```
事件源初始化函数:
```
AioContext *aio_context_new(Error **errp)
{
int ret;
AioContext *ctx;
ctx = (AioContext *) g_source_new(&aio_source_funcs, sizeof(AioContext)); // 事件源的创建,传入的size是AioContext的大小
aio_context_setup(ctx);
/*初始化事件源的其它部分*/
ret = event_notifier_init(&ctx->notifier, false); // 初始化实现事件通知的成员notifier
if (ret < 0) {
error_setg_errno(errp, -ret, "Failed to initialize event notifier");
goto fail;
}
......
}
```
替代Glib中g_main_loop_run事件循环入口函数
```
while (!main_loop_should_exit()) {
main_loop_wait(false);
}
```
选择poll方式
```
/*
如果qemu配置了PPOLL,使用PPOLL实现poll探测,否则使用g_poll实现poll探测
*/
int qemu_poll_ns(GPollFD *fds, guint nfds, int64_t timeout)
{
#ifdef CONFIG_PPOLL
if (timeout < 0) { // 如果timeout小于0,如果IO没有准备好,永远阻塞
return ppoll((struct pollfd *)fds, nfds, NULL, NULL);
} else { // 否则,阻塞一段时间后返回
struct timespec ts;
int64_t tvsec = timeout / 1000000000LL;
/* Avoid possibly overflowing and specifying a negative number of
* seconds, which would turn a very long timeout into a busy-wait.
*/
if (tvsec > (int64_t)INT32_MAX) {
tvsec = INT32_MAX;
}
ts.tv_sec = tvsec;
ts.tv_nsec = timeout % 1000000000LL;
return ppoll((struct pollfd *)fds, nfds, &ts, NULL);
}
#else
return g_poll(fds, nfds, qemu_timeout_ns_to_ms(timeout));
#endif
}
```
qemu自行定义了aio_handlers链表,用于存放AioContext中事件源的处理函数
其第一个成员pfd用于poll查询、监听
[AioHandler结构]
自定义事件源 AioContext中的first_bh为下半部链表
下半部及BH,用于延迟实现功能
将优先级较低的事件的实现进行延迟,使得其他事件有更多的计算机资源。
BH事件触发方式主要为两种:
①执行线程poll到fd准备好之后进行回调执行
②通过eventfd api通知调度
qemu在aio_context_new创建AioContext的时候会通过event_notifier_init初始化EventNotifier,将EventNotifier中的rfd设置aio_set_event_notifier为事件循环要监听的fd。当一个线