天天看點

[轉]Libev源碼分析 -- 整體設計Libev源碼分析 -- 整體設計libev示例libev工作原理總結

Libev源碼分析 -- 整體設計

libev是Marc Lehmann用C寫的高性能事件循環庫。通過libev,可以靈活地把各種事件組織管理起來,如:時鐘、io、信号等。libev在業界内也是廣受好評,不少項目都采用它來做底層的事件循環。node.js也是其中之一。 學習和分析libev庫,有助于了解node.js底層的工作原理,同時也可以學習和借鑒libev的設計思想。本文是最近在學習libev源碼的一些心得總結吧。

libev示例

先上一個例子,看看libev是怎麼使用的吧。

1
2
3
4
5
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55       
// a single header file is required
#include <ev.h>
 #include <stdio.h> // for puts  // every watcher type has its own typedef'd struct // with the name ev_TYPE ev_io stdin_watcher; ev_timer timeout_watcher;  // all watcher callbacks have a similar signature // this callback is called when data is readable on stdin static void stdin_cb (EV_P_ ev_io *w, int revents) {  puts ("stdin ready");  // for one-shot events, one must manually stop the watcher  // with its corresponding stop function.  ev_io_stop (EV_A_ w);   // this causes all nested ev_run's to stop iterating  ev_break (EV_A_ EVBREAK_ALL); }  // another callback, this time for a time-out static void timeout_cb (EV_P_ ev_timer *w, int revents) {  puts ("timeout");  // this causes the innermost ev_run to stop iterating  ev_break (EV_A_ EVBREAK_ONE); }  int main (void) {  // use the default event loop unless you have special needs  struct ev_loop *loop = EV_DEFAULT;   // initialise an io watcher, then start it  // this one will watch for stdin to become readable  ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ 0, EV_READ);  ev_io_start (loop, &stdin_watcher);   // initialise a timer watcher, then start it  // simple non-repeating 5.5 second timeout  ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);  ev_timer_start (loop, &timeout_watcher);   // now wait for events to arrive  ev_run (loop, 0);   // break was called, so exit  return 0; } 
           

這是libev官網文檔的例子,其中libev的使用步驟還是比較清晰的。從main開始入手,可以發現代碼中主要做了這麼幾件事情:

  • 擷取ev_loop執行個體。ev_loop,從名字上可以看出,它代表了一個事件循環,也是我們後面代碼的主要組織者。
  • 建立和初始化watcher。libev中定義了一系列的watcher,每類watcher負責一類特定的事件。一般可以通過ev_TYPE_init函數來建立一個watcher執行個體(TYPE是某一種watcher類型,如:io, timer等)。例子中分别建立了io和timer兩個watcher,并綁定了相應的回調函數。當感興趣的事件發生後,對應的回調函數将會被調用。
  • 将watcher注冊到ev_loop中。一般可以通過ev_TYPE_start函數來完成。注冊成功後,watcher便和loop關聯起來了,當loop中檢測到感興趣的事件發生,便會通知相關的watcher。
  • 啟動事件循環。 即後面的ev_run函數。事件循環啟動後,目前線程/程序将會被阻塞,直到循環被終止。

在上面的例子中,在兩個回調函數中的ev_break函數就是終止循環的地方。當5.5秒逾時或是标準輸入有輸入事件,則會進入到相應的回調函數,然後會終止事件循環,退出程式。

libev工作原理

總的來看,libev其實是實作了Reactor模式。當中主要包含了這麼幾個角色:watcher, ev_loop和ev_run。

watcher

watcher是Reactor中的Event Handler。一方面,它向事件循環提供了統一的調用接口(按類型區分);另一方面,它是外部代碼的注入口,維護着具體的watcher資訊,如:綁定的回調函數,watcher的優先級,是否激活等。

在ev.h中我們可以看到各種watcher的定義,如:ev_io, ev_timer等。其中,watcher的公共屬性定義如下:

1
2
3
4
5
6 7       
/* shared by all watchers */
#define EV_WATCHER(type)         \
 int active; /* private */ \  int pending; /* private */ \  EV_DECL_PRIORITY /* private int priority; */ \  EV_COMMON /* rw void *data; */ \  EV_CB_DECLARE (type) /* private */
           

其中的宏定義如下:

1
2
3
      
# define EV_DECL_PRIORITY int priority;
# define EV_COMMON void *data;
# define EV_CB_DECLARE(type) void (*cb)(EV_P_ struct type *w, int revents); 
           
  • active: 表示目前watcher是否被激活。ev_TYPE_start調用後置位,ev_TYPE_stop調用後複位;
  • pending: 表示目前watcher有事件就緒,等待處理。pending的值其實就是目前watcher在pendings隊列中的下标;
  • priority: 是目前watcher的優先級;
  • data: 附加資料指針,用來在watcher中攜帶額外所需的資料;
  • cb:是事件觸發後的回調函數定義。

具體的watcher再在此基礎上添加額外的屬性。 開發者可以根據需要,選擇特定類型的watcher,建立執行個體并進行初始化,然後将執行個體注冊到loop中即可。libev中定義了若幹種類型的watcher,每類watcher負責解決某一特定領域的問題(如:io, timer, signal等),可以在ev.h中看到這些watcher的定義。

ev_loop

ev_loop則是一個Reactor的角色,是事件循環的上下文環境,就像一根竹簽,把前面的watcher執行個體像糖葫蘆一樣串起來。

ev_loop的定義

ev_loop的定義在ev.c中,具體如下:

1
2
3
4
5
6 7 8       
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 }; 
           

ev_vars.h中定義了ev_loop的各種屬性。在ev_wrap.h中則定義了與loop相關的各種宏,代碼中大多都是以宏的形式進行操作。

watcher的管理

在ev_loop中,watcher按各自的類型進行分類存儲。如:io的loop->anfds,timer的loop->timers。ev_TYPE_start在激活watcher後,便将它加入到相應的存儲結構中(具體的實作在後面介紹watcher的文章再分析)。

在事件循環中,有事件就緒的watcher會被挑揀出來,儲存到ev_loop中。這些就緒的watcher主要由loop->pendings和loop->pendingcnt來維護(如下圖所示)。這兩個東西都是二維數組,第一維都是優先級。pendings中存的是ANPENDING執行個體,後者的做要作用就是維護就緒的watcher指針; 而pendingcnt中存的則是對應優先級上的pendings元素的數量。在每個就緒的watcher上也會有一個pending字段記錄它在pendings清單中的下标,這樣就可以通過watcher很友善的找到它在pendings清單中的位置了,這對删除操作很有幫助。

在一輪事件循環結束後,則會根據優先級,依次觸發就緒的watcher。

[轉]Libev源碼分析 -- 整體設計Libev源碼分析 -- 整體設計libev示例libev工作原理總結

bool ev_run(loop, flag)

ev_run函數是執行事件循環的引擎,即Reactor模式中的select方法。通過向ev_run函數傳遞一個ev_loop執行個體,便可以開啟一個事件循環。ev_run實際上是一個巨大的do-while循環,期間會檢查loop中注冊的各種watcher的事件。如果有事件就緒,則觸發相應的watcher。這個循環會一直持續到ev_break被調用或者無active的watcher為止。當然,也可以通過傳遞EVRUN_NOWAIT或EVRUN_ONCE等flag來控制循環的阻塞行為。

ev_run的工作内容,在官方文檔的API中有詳細說明,通過文檔有助于對ev_run的了解。具體的代碼有點長,在這裡就不貼了,感興趣的同學可以在ev.c中檢視ev_run的實作代碼。剔除掉條件檢查和一些無關緊要的代碼,主要的流程如下圖所示。

[轉]Libev源碼分析 -- 整體設計Libev源碼分析 -- 整體設計libev示例libev工作原理總結

可以看到,ev_run的主要工作就是按watcher的分類,先後檢查各種類型的watcher上的事件,通過ev_feed_event函數将就緒的watcher加入到pending的資料結構中。最後調用ev_invoke_pending,觸發pending中的watcher。完了以後會檢查,是否還有active的watcher以及是否有ev_break調用過,然後決定是否要繼續下一輪循環。

總結

總的來看,libev的結構設計還是非常清晰。如果說,主循環ev_run是libev這棵大樹的主幹,那麼功能強大,數量繁多的watcher就是這棵大樹的樹葉,而循環上下文ev_loop則是連接配接主幹和樹葉的樹枝,它們的分工與職責是相當明确的。作為大樹的主幹,ev_run的代碼是非常穩定和幹淨的,基本上不會摻雜外部開發者的邏輯代碼進來。作為葉子的watcher,它的定位也非常明确:專注于自己的領域,隻解決某一個類型的問題。不同的watcher以及watcher和主循環之間并沒有太多的幹擾和耦合,這也是libev能如此靈活擴充的原因。而中間的樹枝ev_loop的作用也是不言而喻的,正是因為它在中間的調和,前面兩位哥們才能活得這麼有個性。

看到這裡,libev的主體架構已經比較清楚了,但是似乎還沒看到與性能相關的關鍵代碼。與主幹代碼不一樣,這些代碼更多的是隐藏在實作具體邏輯的地方,也就是watcher之中的。雖然watcher的使用接口都比較相似,但是不同的watcher,底層的資料結構和處理政策還是不一樣的。下面一篇文章我們就來探索一下libev中比較常用的幾種watcher的設計與實作。

文章出處:http://codingcat.net/blog/2012/10/09/libev-framework/

轉載于:https://www.cnblogs.com/dszhazha/p/4080497.html

繼續閱讀