事件庫之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"結合咱們的例子進行梳理。async

"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"等文件中。oop

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

代碼結構

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

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

這樣對總體的佈局有個大概的瞭解,就能夠有選擇性的逐個突破了。這裏還能夠結合官方的文檔去了解下每一個函數做用。從而對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等數據結構在後面分析具體監控器是的時候在詳細介紹。後面就要跟進程序的邏輯從而瞭解其設計思想,這樣即可以深刻的瞭解一款組件型的開源軟件了。

相關文章
相關標籤/搜索