事件驅動模型Libev(一)

 

Libev的做者寫了一份很好的官方Manual,比較的齊全,即介紹了Libev的設計思想,也介紹了基本使用還包括內部各種事件詳細介紹。這裏略微贅述一下。Libev經過一個 ·struct ev_loop· 結結構表示一個事件驅動的框架。在這個框架裏面經過ev_xxx結構,ev_initev_xxx_setev_xxx_start接口箱這個事件驅動的框架裏面註冊事件監控器,當相應的事件監控器的事件出現時,便會觸發該事件監控器的處理邏輯,去處理該事件。處理完以後,這些監控器進入到下一輪的監控中。符合一個標準的事件驅動狀態的模型。數據結構

Libev 除了提供了基本的三大類事件(IO事件、定時器事件、信號事件)外還提供了週期事件、子進程事件、文件狀態改變事件等多個事件,這裏咱們用三大基本事件寫一個例子,和Manual上的相似,可是沒有作收尾工做,爲的是將事件的框架清晰的呈現出來。框架

#include<ev.h>
#include <stdio.h>
#include <signal.h>
#include <sys/unistd.h>

ev_io io_w;
ev_timer timer_w;
ev_signal signal_w;

void io_action(struct ev_loop *main_loop,ev_io *io_w,int e)
{
        int rst;
        char buf[1024] = {'\0'};
        puts("in io cb\n");
        read(STDIN_FILENO,buf,sizeof(buf));
        buf[1023] = '\0';
        printf("Read in a string %s \n",buf);
        ev_io_stop(main_loop,io_w);

}

void timer_action(struct ev_loop *main_loop,ev_timer *timer_w,int e)
{
        puts("in tiemr cb \n");
        ev_timer_stop(main_loop,timer_w);

}

void signal_action(struct ev_loop *main_loop,ev_signal signal_w,int e)
{
        puts("in signal cb \n");
        ev_signal_stop(main_loop,signal_w);
        ev_break(main_loop,EVBREAK_ALL);
}

int main(int argc ,char *argv[])
{
        struct ev_loop *main_loop = ev_default_loop(0);
        ev_init(&io_w,io_action);
        ev_io_set(&io_w,STDIN_FILENO,EV_READ);  
        ev_init(&timer_w,timer_action);
        ev_timer_set(&timer_w,2,0);       
        ev_init(&signal_w,signal_action);
        ev_signal_set(&signal_w,SIGINT); 

        ev_io_start(main_loop,&io_w);
        ev_timer_start(main_loop,&timer_w);
        ev_signal_start(main_loop,&signal_w);

        ev_run(main_loop,0);

return 0;
}

 

下面對使用到的這些API進行說明。運維

這裏使用了3種事件監控器,分別監控IO事件、定時器事件以及信號事件。所以定義了3個監控器(watcher),以及觸發監控器時要執行動做的回調函數。Libev定義了多種監控器,命名方式爲 ev_xxx 這裏xxx表明監控器類型,其實現是一個結構體,async

typedef struct ev_io { .... } ev_io; 

經過宏定義能夠簡寫爲 ev_xxx。回調函數的類型爲 void cb_name(struct ev_loop *main_loop,ev_xxx *io_w,int event)函數

在main中,首先定義了一個事件驅動器的結構 struct ev_loop *main_loop 這裏調用 ev_default_loop(0) 生成一個預製的全局驅動器。這裏能夠參考Manual中的選擇。而後依次初始化各個監控器以及設置監控器的觸發條件。oop

初始化監控器的過程是將相應的回調函數即觸發時的動做註冊到監控器上。佈局

設置觸發條件則是該條件產生時纔去執行註冊到監控器上的動做。對於IO事件,通常是設置特定fd上的的可讀或可寫事件,定時器則是多久後觸發。這裏定時器的觸發條件中還有第三參數,表示第一次觸發後,是否循環,若爲0則吧循環,不然按該值循環。信號觸發器則是設置觸發的信號。學習

在初始化並設置好觸發條件後,先調用ev_xxx_start 將監控器註冊到事件驅動器上。接着調用 ev_run 開始事件驅動器。spa

在事件的觸發動做裏面。我加入了一個 ev_xxx_stop 函數,與上面對應,也就是講改監控器從事件驅動器裏面註銷掉。使其再也不起做用。而在信號觸發的動做中還加入了一個 ev_break 該函數可使進程跳出 main_loop 事件驅動器循環,也就是關閉事件驅動器。結束這一邏輯。.net

libev最簡單的示例就是這樣的一個結構。定義一個監控器、書寫觸發動做邏輯、初始化監控器、設置監控器觸發條件、將監控器加入大事件驅動器的循環中便可。一個比較清晰的事件驅動框架。

libev的事件驅動過程能夠想象成以下的僞代碼:

do_some_init()
is_run = True
while is_run:
    t = caculate_loop_time()
    deal_loop(t)
    deal_with_pending_event()
do_some_clear()

 

首先作一些初始化操做,而後進入到循環中,該循環經過一個狀態位來控制是否執行。在循環中,計算出下一次輪詢的時間,這裏輪詢的實現就採用了系統提供的epoll、kqueue等機制。再輪詢結束後檢查有哪些監控器的被觸發了,依次執行觸發動做。這裏不要糾結信號事件、定時器時間咋都通過了 deal_loop libev是如何實現的這裏暫且不討論,這個僞代碼只是大體表示下libev的總體框架。

 

 

Libev源代碼結構

對於畢業生,尤爲是沒有接觸過一些已有工程代碼的新人。拿到一份源碼,怎麼去熟悉它是首要解決的問題。我通常把會把源碼進行分類:一類是產品類的,就好比Redis、Ngnix這一類自己是一個完整的能夠運維的成熟產品;另外一類就是Libev這樣的組件類的。對於組件類的項目,我通常就是分紅這樣幾步:

  1. 有文檔看文檔,沒有文檔問相關人員(包括Google),這個組件主要提供什麼服務
  2. 結合上述信息使用組件的AIP寫個示例程序,跑起來
  3. 大體瀏覽下源碼,分析一下代碼的組織結構
  4. 根據使用的API,進到源碼中看看主幹是怎麼樣實現的,從而瞭解總體思路
  5. 再搜刮源碼,把一些輔助的功能看下,並在例子中嘗試
  6. 以後將整個理解用文字記錄下來。提煉兩大塊內容:實現思想和技巧tips

這裏我對Libev的學習就是依照這樣的一個邏輯一步一步走的。

ev.c代碼結構

「使用Libev」 這篇文章中提到了一個Libev的官方文檔,並根據該文檔寫了個簡單的示例,包括了IO事件、定時器事件以及信號事件這3個最經常使用的事件類型。在本篇文章中將對Libev的代碼結構進行分析。

首先下載Libev的源碼包,下載回來後進行解壓,Libev的源碼都放在同一個目錄中,除去autoconfig產生的文件,代碼文件仍是比較直觀的。主要的.c和.h文件從命名上也查很少能猜出來幹嗎呢。根據咱們的例子,主要抽出其中的"ev.c ev_epoll.c ev_select.c ev.h ev_wrap.c ev_vars.c"結合咱們的例子進行梳理。

「ev_epoll.c"和"ev_select.c"是對系統提供的IO複用機制「epoll」、「select"的支持,還有"poll」、「kqueue」 Solaris的"port"的支持,分別是"ev_poll.c」、「ev_kqueue.c」、「ev_port.c」。具體的框架是相似的,所以只要分析一個其餘的就都瞭解了。

「ev.h」 是對一些API和變量、常量的定義,「ev.c"是Libev的主要邏輯,其中在類型的定義的時候用了一個宏的包裝來聲明成員變量,在文件"ev_vars.c」 中。爲了對成員變量使用的語句進行簡化,就又寫了一個"ev_wrap.c」。所以咱們能夠這樣去看待這些文件,主要邏輯都在"ev.c」,其中部分常量、變量的定義能夠在"ev.h"中,有個結構的成員變量部分的定義在"ev_vars.c"中,同時對該結構成員變量的引用經過"ev_wrap.c"文件作了個簡寫的宏定義;當須要系統提供底層的事件接口時,按分類分別在"ev_epoll.c」、「ev_select.c"等文件中。

接着打開"ev.c"文件,「ev.h"裏面的各類定義,在須要的時候去查詢便可,經過IDE或者Vim/Emacs結合cscope/ctag均可以很好的解決。經過瀏覽能夠發現這些代碼大概能夠分紅三部分:

代碼結構

所以能夠直接跳到代碼部分。分隔點有ecb結束的註釋。這能夠不用擔憂略過的部分,等須要的時候回過去查閱便可。其中ecb的部分,只要知道其API做用便可,無需深究,若是將來須要的時候能夠到這邊來作一個參考。

對於邏輯結構能夠能夠把他分紅幾個部分:

邏輯結構

這樣對總體的佈局有個大概的瞭解,就能夠有選擇性的逐個突破了。這裏還能夠結合官方的文檔去了解下每一個函數做用。從而對Libev的總體提供的服務有個大概的瞭解。

主要數據結構

瀏覽的過程當中梳理下幾個重要的數據結構

1.時間類型

typedef double ev_tstamp; 

2.坑爹的 EV_XX_

Libev用ev_tstamp表示時間單位,其實質就是一個double類型變量。

struct ev_loop;
# define EV_P  struct ev_loop *loop       /* a loop as sole parameter in a declaration */
# define EV_P_ EV_P,                     /* a loop as first of multiple parameters */
# define EV_A  loop                       /* a loop as sole argument to a function call */
# define EV_A_ EV_A,                     /* a loop as first of multiple arguments */
# define EV_DEFAULT_UC  ev_default_loop_uc_ ()        /* the default loop, if initialised, as sole arg */
# define EV_DEFAULT_UC_ EV_DEFAULT_UC,   /* the default loop as first of multiple arguments */
# define EV_DEFAULT  ev_default_loop (0)          /* the default loop as sole arg */
# define EV_DEFAULT_  EV_DEFAULT, /* the default loop as first of multiple arguments */

 

這裏的定義仍是比較讓人無解的。「EV_XXX」 等同於 EV_XXX,,這樣在後續的API使用中,會顯的更簡潔一些,好比針對第一個參數是struct ev_loop *loop 的回調函數的書寫,就能夠寫成 · void io_action(EV_P ev_io *io_w,int e)· 。這裏不知道做者還有沒有其餘用以,這裏我不是很推薦,可是要知道,後面再看代碼的時候才更容易理解。

3.各類watcher

基類

首先看一個ev_watcher,這個咱們能夠用OO思想去理解他,他就至關於一個基類,後續的ev_io什麼的都是派生自該機構,這裏利用了編譯器的一個「潛規則」就是變量的定義順序與聲明順序一致。這一點在libuv裏面也用了,而後大神雲風哥還對其吐槽了一番,能夠參見雲風的blog。這裏我儘可能吧全部宏包裹的部分都撥出來,方便理解和看。看過Libev的代碼,我想在驚歎其宏的高明之餘必定也吐槽過。

typedef struct ev_watcher { int active; int pending; int priority; void *data; void (*cb)(struct ev_loop *loop, struct ev_watcher *w, int revents); } ev_watcher; 

與基類配套的還有個裝監控器的List。

typedef struct ev_watcher_list { int active; int pending; int priority; void *data; void (*cb)(struct ev_loop *loop, struct ev_watcher_list *w, int revents); struct ev_watcher_list *next; } ev_watcher_list; 
IO監控器
typedef struct ev_io { int active; int pending; int priority; void *data; void (*cb)(struct ev_loop *loop, struct ev_io *w, int revents); struct ev_watcher_list *next; int fd; /* 這裏的fd,events就是派生類的私有成員,分別表示監聽的文件fd和觸發的事件(可讀仍是可寫) */ int events; } ev_io; 

在這裏,經過從宏中剝離出來後,能夠看到將派生類的私有變量放在了共有部分的後面。這樣,當使用C的指針強制轉換後,一個指向 struct ev_io對象的基類 ev_watcher 的指針p就能夠經過 p->active 訪問到派生類中一樣表示active的成員了。

定時器watcher
typedef struct ev_watcher_time { int active; int pending; int priority; void *data; void (*cb)(struct ev_loop *loop, struct ev_watcher_time *w, int revents); ev_tstamp at; /* 這個at就是派生類中新的自有成員 ,表示的是at時間觸發 */ } ev_watcher_time; 

這裏定時器事件watcher和IO的不同的地方在於,對於定時器會用專門的最小堆去管理。而IO和信號等其餘事件的監控器則是經過單鏈表掛起來的,所以他沒有next成員。

信號watcher
typedef struct ev_signal { int active; int pending; int priority; void *data; void (*cb)(struct ev_loop *loop, struct ev_signal *w, int revents); struct ev_watcher_list *next; int signum; /* 這個signum就是派生類中新的自有成員 ,表示的是接收到的信號,和定時器中的at相似 */ } ev_signal; 

還有其餘的事件watcher的數據結構也是和這個相似的,能夠對着"ev.h"的代碼找一下,這裏再也不贅述了。最後看一個能夠容納全部監控器對象的類型:

union ev_any_watcher
{
  struct ev_watcher w; struct ev_watcher_list wl; struct ev_io io; struct ev_timer timer; struct ev_periodic periodic; struct ev_signal signal; struct ev_child child; struct ev_stat stat; struct ev_idle idle; struct ev_prepare prepare; struct ev_check check; struct ev_fork fork; struct ev_cleanup cleanup; struct ev_embed embed; struct ev_async async; }; 

4.最重要的 ev_loop

在上面就已經看到了 struct ev_loop 的前向聲明瞭,那麼他究竟是怎樣的一個結構的?在「ev.c」裏面能夠看到這樣的定義:

struct ev_loop { ev_tstamp ev_rt_now; #define ev_rt_now ((loop)->ev_rt_now) #define VAR(name,decl) decl; #include "ev_vars.h" #undef VAR }; #include "ev_wrap.h" 

以前說過的 「ev_vars.h"和"ev_wrap.h"是爲了定義一個數據結構及簡化訪問其成員的,就是說的這個 ev_loop 結構體。 這裏用的宏爲:

#define VAR(name,decl) decl; #define VARx(type,name) VAR(name, type name) 

展開就是

#define VARx(type,name) type name 

而後再看"ev_vars.h」 ,裏面都是 類型-變量的 VARx的宏,這樣再將其include 到結構體的定義中。這樣就能夠當作該結構定義爲:

struct ev_loop
{
    ev_tstamp ev_rt_now;
    ev_tstamp now_floor;
    int rfeedmax;
    ... .........; } 

不知道做者的用意何在,目前尚未看到這樣作的好處在哪裏。

而後 #define ev_rt_now ((loop)->ev_rt_now) 能夠和後面的 「ev_warp.h"一塊兒看。實際上就是 #define xxx ((loop)->xxx) 這樣在要用struct ev_loop 的一個實例對象loop的成員時,就能夠直接寫成xxx了,這裏再聯想到以前的 EV_P EV_P_ EV_A EV_A_ ,就會發現,在Libev的內部函數中,這樣的配套就可使代碼簡潔很多。不過這樣也增長了第一次閱讀其的門檻。相信沒有看過Libev不說其晦澀的。

5.重要的全局變量

default_loop_struct

在"ev.c"中有

static struct ev_loop default_loop_struct; 

這個就是strct loop的一個實例對象,表示的是預製事件驅動器。若是在代碼中使用的是預製事件驅動器,那麼後續的操做就都圍繞着這個數據結構展開了。

爲了操做方便,還定義了指向該對象的一個全局指針:

struct ev_loop *ev_default_loop_ptr 

代碼的框架和主要的數據結構梳理出來了,還有ANFD、ANHEAP等數據結構在後面分析具體監控器是的時候在詳細介紹。後面就要跟進程序的邏輯從而瞭解其設計思想,這樣即可以深刻的瞭解一款組件型的開源軟件了。

 

轉載:http://my.oschina.net/u/917596/blog/176915

相關文章
相關標籤/搜索