libev 源碼淺析


libev是一個開源的事件驅動庫,基於epoll,kqueue等OS提供的基礎設施。其以高效出名,它可以將IO事件,定時器,和信號統一起來,統一放在事件處理這一套框架下處理。

 libev的基本使用方法如下:

 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);// 設置對stdin_watcher這個fd關注讀事件,並指定回調函數
     ev_io_start (loop, &stdin_watcher);// 激活stdin_watcher這個fd,將其設置到loop中

     // initialise a timer watcher, then start it
     // simple non-repeating 5.5 second timeout
     ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);//設置一個定時器,並指定一個回調函數,這個timer只執行一次,5.5s后執行
     ev_timer_start (loop, &timeout_watcher);//激活這個定時器,將其設置到loop中

     // now wait for events to arrive
     ev_run (loop, 0);//循環開始

     // break was called, so exit
     return 0;
   }

libev中有一個抽象概念,叫做watcher(ev_watcher),libev中有各種各樣的watcher,比如定時器watcher(struct ev_timer),I/O watcher(struct ev_io) , 信號watcher(struct ev_signal)

等等。這三個具體的watcher相當於父類watcher的子類。這種繼承關系在C++這種高級語言中已內置,在C中實現就需要一些技巧,libev的作者使用了宏。

"父類"ev_watcher定義如下:

typedef struct ev_watcher
{
   EV_WATCHER(ev_watcher)
} ev_watcher;

宏EV_WATCHER定義如下:

/* shared by all watchers */
 #define EV_WATCHER(type)      \                                                                                                                                                           
    int active; /* private */     \     //該watcher是否被激活,加入到loop中
    int pending; /* private */      \   //該watcher關注的events是否已觸發
    EV_DECL_PRIORITY /* private */    \ //int priority; 優先級,watcher是有優先級的
    EV_COMMON /* rw */        \         // void *data;   
    EV_CB_DECLARE (type) /* private */  // void (*cb)(struct ev_loop *loop, type *w, int revents);回調函數

再看一個"父類"ev_watcher_list的定義:

 typedef struct ev_watcher_list
 {
   EV_WATCHER_LIST (ev_watcher_list)
 } ev_watcher_list; 

宏EV_WATCHER_LIST定義如下:

#define EV_WATCHER_LIST(type)     \                                                                                                                                                       
   EV_WATCHER (type)       \
   struct ev_watcher_list *next; /* private */

可以看出,ev_watcher_list 其實也是ev_watcher的一個"子類", 它多了一個成員變量 struct  ev_watcher_list *next;

這個成員變量用於將watcher串起來。

 現在看一個I/O watcher 這個最重要的"子類":

 typedef struct ev_io
 {
   EV_WATCHER_LIST (ev_io)
 
   int fd;     /* ro */ // 顯而易見,與io相關聯的fd
   int events; /* ro */ // 這個watcher在fd上關注的事件
 } ev_io;

可以看出,ev_io是一種具體的watcher,它有兩個自己專有的成員變量fd和events

下面看一下最關鍵的一個數據結構:

   struct ev_loop
   {
     ev_tstamp ev_rt_now;
     #define ev_rt_now ((loop)->ev_rt_now)
     // 這里decl是declare的意思,ev_vars.h 里面會定義一堆的變量,這些變量
     // 都是本結構的成員,ev_vars.h展開的時候會用到下面這一行VAR的宏
     #define VAR(name,decl) decl;
       #include "ev_vars.h"
     #undef VAR
   };

ev_vars.h中包含很多關鍵的成員,比如:

epoll相關的成員變量:

#if EV_USE_EPOLL || EV_GENWRAP
VARx(struct epoll_event *, epoll_events)  // 相當於struct epoll_event *epoll_events                                            
VARx(int, epoll_eventmax) //目前epoll_events數組的大小,可以擴充,每次以2倍的大小擴充
VARx(int, backend_fd) // 對於epoll來說,就是epoll使用的fd
//對於epoll來說,實際的函數是ev_epoll.c中的epoll_modify函數,這個函數會執行epoll_ctl
VAR (backend_modify, void (*backend_modify)(EV_P_ int fd, int oev, int nev))
//對於epoll來說,實際的函數是ev_poll.c中的epoll_poll函數,這個函數會執行epoll_wait VAR (backend_poll , void (*backend_poll)(EV_P_ ev_tstamp timeout))

與fd相關的成員變量:

VARx(ANFD *, anfds)//這個數組是以fd為索引
VARx(int, anfdmax) //上面數組的大小
VARx(int *, fdchanges) // fdchangemax大小的數組,每個元素是一個fd,這個數組中存了所有epoll需要poll的fd
VARx(int, fdchangemax) //數組的容量
VARx(int, fdchangecnt) // 數組中實際的元素的大小

ANFD和fd一一對應,結構體ANFD如下:

 typedef struct
 {
   WL head; // typedef ev_watcher_list *WL; 關注同一個fd的事件的watcher的鏈表,一個fd可以有多個watcher監聽它的事件
   unsigned char events; /* the events watched for */ // watcher鏈表中所有watcher關注的事件的按位與
   unsigned char reify;  /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET) */ //當這個結構體需要重新epoll_ctl則設置,說明關注的事件發生了變化
   unsigned char emask;  /* the epoll backend stores the actual kernel mask in here */ //實際發生的事件
   unsigned char unused;
 #if EV_USE_EPOLL
   unsigned int egen;    /* generation counter to counter epoll bugs */
 #endif
 #if EV_SELECT_IS_WINSOCKET || EV_USE_IOCP
   SOCKET handle;
 #endif
 #if EV_USE_IOCP
   OVERLAPPED or, ow;
 #endif
 } ANFD;

維護所有的"所關注的事件發生了的watcher",這些watcher的callback最后都需要被調用

VAR (pendings, ANPENDING *pendings [NUMPRI])  //watcher是有優先級的,libev為每個優先級的watcher維護一個數組
VAR (pendingmax, int pendingmax [NUMPRI]) // 每個優先級watcher數組的容量
VAR (pendingcnt, int pendingcnt [NUMPRI]) //  每個優先級watcher數組實際大小

struct ANPENDING如下:

typedef struct
{
  W w; //typedef ev_watcher *W;
  int events; /* the pending event set for the given watcher */  //events只指這個watcher關注了的並且已經發生了的還沒有處理的事件
} ANPENDING;

 

從示例程序可以看出,使用libev主要有幾個方法:

ev_io_init

ev_io_start

ev_timer_init

ev_timer_start

ev_run

一個個看:

ev_io_init是個宏,主要就是設置ev_io中的各個成員

void ev_io_start(struct ev_loop *loop, ev_io *w) 會做如下幾件事情:

1.  將參數w表示的watcher激活(w->active=1)

2.  將watcher w 加入到 w所關注的fd在anfds[](loop中)中相應的位置處的結構體ANFD中的watcher list鏈表中。

3.  將w所關注的fd加入到int *fdchanges數組中。

void ev_run(struct ev_loop *loop, int flags)最重要的函數,做如下幾件事:
1. 調用fd_reify。
    遍歷fdchanges數組:針對其中的每個fd,做如下事情:
    將關注這個fd的所有的watcher都從ANFD的鏈表中取出來(通過fd去anfds[]中找到相應的ANFD結構體),然后將所有這些watcher關注的events通過epoll_ctl 給設置到
    kernel中。然后清空fdchanges數組,其實就是將fdchangecnt置為0。
2. time_update 更新時間,校准時間
3. 計算給epoll_wait()使用的timeout,從維護所有的timer的最小堆中取堆頂(timers [HEAP0]),然后減去當前時間得到timeout。最小堆使用一個數組實現的,每個元素是
struct ANHE。
    timers定義如下:
    
VARx(ANHE *, timers)

  struct ANHE定義如下:

/* a heap element */
typedef struct {
   ev_tstamp at;//timer watcher 到期時間
   WT w;// typedef ev_watcher_time *WT;
} ANHE;
typedef struct ev_watcher_time
{
   EV_WATCHER_TIME (ev_watcher_time)
} ev_watcher_time;

#define EV_WATCHER_TIME(type) \ 
 EV_WATCHER (type) \
 ev_tstamp at; /* private */

4. 調用backend_poll(loop, waittime)會做如下幾件事:

    對於使用epoll的系統來說,它實際上是調用ev_epoll.c 中的epoll_poll()函數,這個函數主要流程如下:

    4.1. 調用epoll_wait()
    4.2. 遍歷每個返回的 struct epoll_event,取出fd,和epoll_event中激活的事件,調用fd_event (loop, fd, got)函數。
            這個函數會更新loop的pending數組,將那些已經關注了一些事件的watcher,並且確實發生了的這些watcher給設置到loop的pending數組中
            注意這里,got是所有watcher關注的事件的總和。ANPENDING結構體中的events僅僅單個watcher關注的events。
5. time_update 再次更新校准時間
6. timers_reify(loop) 如果最近的定時器已經觸發過了,則重新選出下一個最近的定時器,將其置於堆頂。
7.periodics_reify(loop) 和timers處理差不多
8. idle_reify(loop) 沒關注
9. EV_INVOKE_PENDING 從loop的pending數組中依次調用各個watcher的回調函數,優先級從高到低
10.如果當前loop中還有被激活的watcher,並且loop_done ==0 並且啟動ev_run(flags)的參數沒有設置EVRUN_ONCE和EVRUN_NOWAIT,則繼續從1開始。
 
到目前為止,已經將timer和I/O事件進行統一了,現在看信號是如何統一到事件處理框架中的。
看看signal watcher的定義:
 
/* invoked when the given signal has been received */                                                                                                                        
/* revent EV_SIGNAL */
typedef struct ev_signal
{
   EV_WATCHER_LIST (ev_signal)
   int signum; /* ro */ //信號id
 } ev_signal;

ANSIG signals [EV_NSIG - 1]; // 每個信號的信息用一個ANSIG結構體表示
typedef struct
{
  EV_ATOMIC_T pending;
#if EV_MULTIPLICITY
  EV_P;
#endif
  WL head;//可以有多個watcher關注同一個信號,使用鏈表串起來
}ANSIG;
loop中和信號相關的主要成員如下:
 
VAR (evpipe, int evpipe [2])// 用於將信號處理和事件處理框架結合在一起,evpipe[0]用於讀,evpipe[1]用於寫
VARx(ev_io, pipe_w) //這個I/O ev就是用於封裝上面的pipe的讀端,讓epoll監聽這個pipe_w,當接收到信號的時候,只需要在信號處理函數中往evpipe[1]中寫即可

  

和I/O事件,timer事件類似,有如下兩個過程:
ev_signal_init : 宏,初始化這個ev_signal結構體
void ev_signal_start(struct ev_loop *loop, ev_signal *w)做如下幾件事:
1. 激活ev_signal這個watcher(w->active = 1)
2. 將w串到signals數組中相應位置的watcher list中
3. 初始化一對pipe,這對pipe就是用於將信號和事件處理框架結合起來,並且注冊一個ev_io pipe_w到epoll中,pipe_w封裝的是int evpipe[2]的讀端evpipe[0]
4. 注冊這個信號的信號處理函數ev_sighandler,信號處理函數會將signals數組中相應的槽位的pending置1,意思是已接收到這個信號,並且往int evpipe[2]的寫端
evpipe[1]寫入一個字節,這樣在pipe_w這個IO event就有了讀事件,從而epoll_wait()返回,成功的將信號和事件處理框架結合起來。
 
 
至此,libev如何將IO 事件,timer事件和信號統一到事件處理框架下已分析完成。可以看出,libev和libevent的做法是一樣的,只是實現不一樣,比如,關於繼承的實現,libev使用宏來實現相當的難讀,但是這樣做是高效的,借用霸爺的話說:"在libev的世界中,效率第一"。這可能正是大多數人說,libev比libevent代碼難讀的原因
 
 
 
 
 
 
        

參考資料: 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM