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(),其狀態轉換圖如下:
下面分別簡單介紹一下這四個函數的作用:
- g_main_context_prepare():對於沒有設置G_SOURCE_READY標志的source,調用source->source_funcs->prepare函數,如果返回TRUE,則設置source的G_SOURCE_READY;調用prepare函數時會通過參數返回一個超時時間,選取最小的一個超時時間賦值給context->timeout。
- g_main_context_query():從context->poll_records中返回指定個數的fd,返回 context->timeout,context->poll_changed設為FALSE.
- 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中;
- g_main_context_dispatch():清除 context->pending_dispatches 中 source 的 G_SOURCE_READY 標志,然后調用其 dispatch 函數;
上面就是整個事件的處理流程,我們需要做的就是把新的source加入到這個處理流程中,glib會負責處理source上注冊的各種事件源。Glib中有兩個添加函數,分別實現將source 加入到GMaincontext和將fd加入到source的功能:
- g_source_attach(): 將 source->poll_fds中的文件描述符加入到 context->poll_records中;source添加到 context 的source 鏈表中;
- 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() 中被調用。下面分別介紹一下這幾個函數的主要功能:
- 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。
參考:
- Qemu/docs/multiple-iothreads.txt
- https://developer.gnome.org/glib/2.46/glib-The-Main-Event-Loop.html