Qemu事件處理機制簡介

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 中的事件處理

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

  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 {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() 中被調用。下面分別介紹一下這幾個函數的主要功能:

  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
相關文章
相關標籤/搜索