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。
參考: