Qemu事件处理机制简介

Qmeu 采用了基于事件驱动的架构,所有的事件都在一个事件循环(event loop)中被处理,系统中默认的事件循环是在main-loop.c 中的主循环(main loop)。我们也可以使用 –object iothread,id=my-iothread自己创建事件循环。

Qemu 中的事件架构来源于glib,其实qemu本身就是基于glib的,qemu中有大量的概念来源于glib,所以在学习qemu之前先了解一下glib有助于更快的理解qemu。下面首先介绍一下glib中的事件机制。

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()函数来添加文件描述符。

主上下文的一次循环包含四个步骤,分别由四个函数实现:g_main_context_prepare(), g_main_context_query(), g_main_context_check() 和 g_main_context_dispatch(),其状态转换图如下:

下面分别简单介绍一下这四个函数的作用:

  1. g_main_context_prepare():对于没有设置G_SOURCE_READY标志的source,调用source->source_funcs->prepare函数,如果返回TRUE,则设置source的G_SOURCE_READY;调用prepare函数时会通过参数返回一个超时时间,选取最小的一个超时时间赋值给context->timeout。
  2. g_main_context_query():从context->poll_records中返回指定个数的fd,返回 context->timeout,context->poll_changed设为FALSE.
  3. g_main_context_check():如果context->poll_changed 为TRUE,则返回FALSE;否则复制传入的fds的revents到相应的 context->poll_records->fd->revents中;遍历所有的source,对未设置 G_SOURCE_READY 标志的source调用其 check 函数;将已经设置 G_SOURCE_READY 标志的source 添加到 context->pending_dispatches中;
  4. g_main_context_dispatch():清除 context->pending_dispatches 中 source 的 G_SOURCE_READY 标志,然后调用其 dispatch 函数;

上面就是整个事件的处理流程,我们需要做的就是把新的source加入到这个处理流程中,glib会负责处理source上注册的各种事件源。Glib中有两个添加函数,分别实现将source 加入到GMaincontext和将fd加入到source的功能:

  1. g_source_attach(): 将 source->poll_fds中的文件描述符加入到 context->poll_records中;source添加到 context 的source 链表中;
  2. g_source_add_poll(): 将 fd 加入到 source->poll_fds中,然后再加入到 context->poll_records中,设置 context->poll_changed 为 TRUE.

Qemu 中的事件处理

下面介绍一下qemu 是如何使用这一套事件处理流程的。Qemu是基于glib 开发的,继承了很多glib的概念,struct AioContext 就是按照 glib 的source 创建原则新建的一个事件源类型,用来处理信号,中断等事件,其内容如下:

struct AioContext {

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() 中被调用。下面分别介绍一下这几个函数的主要功能:

  1. aio_ctx_prepare 会调用 aio_compute_timeout 来计算需要的超时时间,这个超时时间是在轮询过程中使用的,它是由 AioContext 中注册的bh的属性决定的,当AioContext 中注册的所有的bh 都是空闲的时,则返回一个有效的超时时间;当至少有一个bh不是空闲的时,则返回0,从而保证bh会被尽快执行。

      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_aio_context: VNC,QMP 命令
  • Iohandler->ctx:负责监控信号,中断,事件通知,socket等;
  • Iothread->ctx:主要负责io方面的监控;
  • Bs->ctx:负责blockjob等相关任务的监控

Qemu在初始化的过程中用 g_source_attach 函数把 qemu_aio_context和iohandler->ctx 添加到主循环。

新建qemu事件处理循环

上面是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。

参考:

  1. Qemu/docs/multiple-iothreads.txt
  2. https://developer.gnome.org/glib/2.46/glib-The-Main-Event-Loop.html
原文地址:https://www.cnblogs.com/baiyw/p/6061689.html