##Libev源代碼結構 對於畢業生,尤爲是沒有接觸過一些已有工程代碼的新人。拿到一份源碼,怎麼去熟悉它是首要解決的問題。我通常把會把源碼進行分類:一類是產品類的,就好比Redis、Ngnix這一類自己是一個完整的能夠運維的成熟產品;另外一類就是Libev這樣的組件類的。對於組件類的項目,我通常就是分紅這樣幾步:數據結構
這裏我對Libev的學習就是依照這樣的一個邏輯一步一步走的。框架
在「使用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的總體提供的服務有個大概的瞭解。設計
瀏覽的過程當中梳理下幾個重要的數據結構
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)· 。這裏不知道做者還有沒有其餘用以,這裏我不是很推薦,可是要知道,後面再看代碼的時候才更容易理解。
首先看一個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;
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的成員了。
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成員。
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; };
在上面就已經看到了 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不說其晦澀的。
在"ev.c"中有
static struct ev_loop default_loop_struct;
這個就是strct loop的一個實例對象,表示的是預製事件驅動器。若是在代碼中使用的是預製事件驅動器,那麼後續的操做就都圍繞着這個數據結構展開了。
爲了操做方便,還定義了指向該對象的一個全局指針:
struct ev_loop *ev_default_loop_ptr
代碼的框架和主要的數據結構梳理出來了,還有ANFD、ANHEAP等數據結構在後面分析具體監控器是的時候在詳細介紹。後面就要跟進程序的邏輯從而瞭解其設計思想,這樣即可以深刻的瞭解一款組件型的開源軟件了。