1. 程式人生 > >libevent原始碼分析--事件處理框架

libevent原始碼分析--事件處理框架

前面已經對libevent的事件處理框架和event結構體做了描述,現在是時候剖析libevent對事件的詳細處理流程了,本節將分析libevent的事件處理框架event_base和libevent註冊、刪除事件的具體流程,可結合前一節libevent對event的管理。

1 事件處理框架-event_base

回想Reactor模式的幾個基本元件,本節講解的部分對應於Reactor框架元件。在libevent中,這就表現為event_base結構體,結構體宣告如下,它位於event-internal.h檔案中:

  1. struct event_base {  
  2.  conststruct eventop *evsel;  
  3.  void *evbase;   
  4.  int event_count;  /* counts number of total events */
  5.  int event_count_active; /* counts number of active events */
  6.  int event_gotterm;  /* Set to terminate loop */
  7.  int event_break;  /* Set to terminate loop immediately */
  8.  /* active event management */
  9.  struct event_list **activequeues;  
  10.  int nactivequeues;  
  11.  /* signal handling info */
  12.  struct evsignal_info sig;  
  13.  struct event_list eventqueue;  
  14.  struct timeval event_tv;  
  15.  struct
     min_heap timeheap;  
  16.  struct timeval tv_cache;  
  17. };  

下面詳細解釋一下結構體中各欄位的含義。
1)evsel和evbase這兩個欄位的設定可能會讓人有些迷惑,這裡你可以把evsel和evbase看作是類和靜態函式的關係,比如新增事件時的呼叫行為:evsel->add(evbase, ev),實際執行操作的是evbase;這相當於class::add(instance, ev),instance就是class的一個物件例項。
evsel指向了全域性變數static const struct eventop *eventops[]中的一個;
前面也說過,libevent將系統提供的I/O demultiplex機制統一封裝成了eventop結構;因此eventops[]包含了select、poll、kequeue和epoll等等其中的若干個全域性例項物件。
evbase實際上是一個eventop例項物件;
先來看看eventop結構體,它的成員是一系列的函式指標, 在event-internal.h檔案中:
struct eventop {
 const char *name;
 void *(*init)(struct event_base *); // 初始化
 int (*add)(void *, struct event *); // 註冊事件
 int (*del)(void *, struct event *); // 刪除事件
 int (*dispatch)(struct event_base *, void *, struct timeval *); // 事件分發
 void (*dealloc)(struct event_base *, void *); // 登出,釋放資源
 /* set if we need to reinitialize the event base */
 int need_reinit;
};
也就是說,在libevent中,每種I/O demultiplex機制的實現都必須提供這五個函式介面,來完成自身的初始化、銷燬釋放;對事件的註冊、登出和分發。
比如對於epoll,libevent實現了5個對應的介面函式,並在初始化時並將eventop的5個函式指標指向這5個函式,那麼程式就可以使用epoll作為I/O demultiplex機制了,這個在後面會再次提到。
2)activequeues是一個二級指標,前面講過libevent支援事件優先順序,因此你可以把它看作是陣列,其中的元素activequeues[priority]是一個連結串列,連結串列的每個節點指向一個優先順序為priority的就緒事件event。
3)eventqueue,連結串列,儲存了所有的註冊事件event的指標。
4)sig是由來管理訊號的結構體,將在後面訊號處理時專門講解;
5)timeheap是管理定時事件的小根堆,將在後面定時事件處理時專門講解;
6)event_tv和tv_cache是libevent用於時間管理的變數,將在後面講到;
其它各個變數都能因名知意,就不再囉嗦了。

2 建立和初始化event_base

建立一個event_base物件也既是建立了一個新的libevent例項,程式需要通過呼叫event_init()(內部呼叫event_base_new函式執行具體操作)函式來建立,該函式同時還對新生成的libevent例項進行了初始化。
該函式首先為event_base例項申請空間,然後初始化timer mini-heap,選擇並初始化合適的系統I/O 的demultiplexer機制,初始化各事件連結串列;
函式還檢測了系統的時間設定,為後面的時間管理打下基礎。

3 介面函式

前面提到Reactor框架的作用就是提供事件的註冊、登出介面;根據系統提供的事件多路分發機制執行事件迴圈,當有事件進入“就緒”狀態時,呼叫註冊事件的回撥函式來處理事件。
Libevent中對應的介面函式主要就是:

  1. int  event_add(struct event *ev, conststruct timeval *timeout);  
  2. int  event_del(struct event *ev);  
  3. int  event_base_loop(struct event_base *base, int loops);  
  4. void event_active(struct event *event, int res, short events);  
  5. void event_process_active(struct event_base *base);   

本節將按介紹事件註冊和刪除的程式碼流程,libevent的事件迴圈框架將在下一節再具體描述。
對於定時事件,這些函式將呼叫timer heap管理介面執行插入和刪除操作;對於I/O和Signal事件將呼叫eventopadd和delete介面函式執行插入和刪除操作(eventop會對Signal事件呼叫Signal處理介面執行操作);這些元件將在後面的內容描述。
1)註冊事件
函式原型:
int event_add(struct event *ev, const struct timeval *tv)
引數:ev:指向要註冊的事件;
tv:超時時間;
函式將ev註冊到ev->ev_base上,事件型別由ev->ev_events指明,如果註冊成功,ev將被插入到已註冊連結串列中;如果tv不是NULL,則會同時註冊定時事件,將ev新增到timer堆上;
如果其中有一步操作失敗,那麼函式保證沒有事件會被註冊,可以講這相當於一個原子操作。這個函式也體現了libevent細節之處的巧妙設計,且仔細看程式程式碼,部分有省略,註釋直接附在程式碼中。

  1. int event_add(struct event *ev, conststruct timeval *tv)  
  2. {  
  3.  struct event_base *base = ev->ev_base; // 要註冊到的event_base
  4.  conststruct eventop *evsel = base->evsel;  
  5.  void *evbase = base->evbase; // base使用的系統I/O策略
  6.  // 新的timer事件,呼叫timer heap介面在堆上預留一個位置
  7.  // 注:這樣能保證該操作的原子性:
  8.  // 向系統I/O機制註冊可能會失敗,而當在堆上預留成功後,
  9.  // 定時事件的新增將肯定不會失敗;
  10.  // 而預留位置的可能結果是堆擴充,但是內部元素並不會改變
  11.  if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {  
  12.   if (min_heap_reserve(&base->timeheap,  
  13.    1 + min_heap_size(&base->timeheap)) == -1)  
  14.    return (-1);  /* ENOMEM == errno */
  15.  }  
  16.  // 如果事件ev不在已註冊或者啟用連結串列中,則呼叫evbase註冊事件
  17.  if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&  
  18.   !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {  
  19.    res = evsel->add(evbase, ev);  
  20.    if (res != -1) // 註冊成功,插入event到已註冊連結串列中
  21.     event_queue_insert(base, ev, EVLIST_INSERTED);  
  22.  }  
  23.  // 準備新增定時事件
  24.  if (res != -1 && tv != NULL) {  
  25.   struct timeval now;  
  26.   // EVLIST_TIMEOUT表明event已經在定時器堆中了,刪除舊的
  27.   if (ev->ev_flags & EVLIST_TIMEOUT)  
  28.    event_queue_remove(base, ev, EVLIST_TIMEOUT);  
  29.   // 如果事件已經是就緒狀態則從啟用連結串列中刪除
  30.   if ((ev->ev_flags & EVLIST_ACTIVE) &&  
  31.    (ev->ev_res & EV_TIMEOUT)) {  
  32.     // 將ev_callback呼叫次數設定為0
  33.     if (ev->ev_ncalls && ev->ev_pncalls) {  
  34.      *ev->ev_pncalls = 0;  
  35.     }  
  36.     event_queue_remove(base, ev, EVLIST_ACTIVE);  
  37.   }  
  38.   // 計算時間,並插入到timer小根堆中
  39.   gettime(base, &now);  
  40.   evutil_timeradd(&now, tv, &ev->ev_timeout);  
  41.   event_queue_insert(base, ev, EVLIST_TIMEOUT);  
  42.  }  
  43.  return (res);  
  44. }  
  45. event_queue_insert()負責將事件插入到對應的連結串列中,下面是程式程式碼;  
  46. event_queue_remove()負責將事件從對應的連結串列中刪除,這裡就不再重複貼程式碼了;  
  47. void event_queue_insert(struct event_base *base, struct event *ev, int queue)  
  48. {  
  49.  // ev可能已經在啟用列表中了,避免重複插入
  50.  if (ev->ev_flags & queue) {  
  51.   if (queue & EVLIST_ACTIVE)  
  52.    return;  
  53.  }  
  54.  // ...
  55.  ev->ev_flags |= queue; // 記錄queue標記
  56.  switch (queue) {  
  57.  case EVLIST_INSERTED: // I/O或Signal事件,加入已註冊事件連結串列
  58.   TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);  
  59.   break;  
  60.  case EVLIST_ACTIVE: // 就緒事件,加入啟用連結串列
  61.   base->event_count_active++;  
  62.   TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], ev, ev_active_next);  
  63.   break;  
  64.  case EVLIST_TIMEOUT: // 定時事件,加入堆
  65.   min_heap_push(&base->timeheap, ev);  
  66.   break;  
  67.  }  
  68. }  

2)刪除事件:
函式原型為:int  event_del(struct event *ev);
該函式將刪除事件ev,對於I/O事件,從I/O 的demultiplexer上將事件登出;對於Signal事件,將從Signal事件連結串列中刪除;對於定時事件,將從堆上刪除;
同樣刪除事件的操作則不一定是原子的,比如刪除時間事件之後,有可能從系統I/O機制中登出會失敗。

  1. int event_del(struct event *ev)  
  2. {  
  3.  struct event_base *base;  
  4.  conststruct eventop *evsel;  
  5.  void *evbase;  
  6.  // ev_base為NULL,表明ev沒有被註冊
  7.  if (ev->ev_base == NULL)  
  8.   return (-1);  
  9.  // 取得ev註冊的event_base和eventop指標
  10.  base = ev->ev_base;  
  11.  evsel = base->evsel;  
  12.  evbase = base->evbase;  
  13.  // 將ev_callback呼叫次數設定為
  14.  if (ev->ev_ncalls && ev->ev_pncalls) {  
  15.   *ev->ev_pncalls = 0;  
  16.  }  
  17.  // 從對應的連結串列中刪除
  18.  if (ev->ev_flags & EVLIST_TIMEOUT)  
  19.   event_queue_remove(base, ev, EVLIST_TIMEOUT);  
  20.  if (ev->ev_flags & EVLIST_ACTIVE)  
  21.   event_queue_remove(base, ev, EVLIST_ACTIVE);  
  22.  if (ev->ev_flags & EVLIST_INSERTED) {  
  23.   event_queue_remove(base, ev, EVLIST_INSERTED);  
  24.   // EVLIST_INSERTED表明是I/O或者Signal事件,
  25.   // 需要呼叫I/O demultiplexer登出事件
  26.   return (evsel->del(evbase, ev));  
  27.  }  
  28.  return (0);  
  29. }  

4 小節
分析了event_base這一重要結構體,初步看到了libevent對系統的I/O demultiplex機制的封裝event_op結構,並結合原始碼分析了事件的註冊和刪除處理,下面將會接著分析事件管理框架中的主事件迴圈部分。