天天看點

libevent源碼分析--事件處理架構

前面已經對libevent的事件處理架構和event結構體做了描述,現在是時候剖析libevent

對事件的詳細處理流程了,本節将分析libevent的事件處理架構event_base和libevent注冊、

删除事件的具體流程,可結合前一節libevent對event的管理。

1 事件處理架構-event_base

回想Reactor模式的幾個基本元件,本節講解的部分對應于Reactor架構元件。在libevent

中,這就表現為event_base結構體,結構體聲明如下,它位于event-internal.h檔案中:

struct event_base{
const struct eventop*evsel;
void*evbase;  
int event_count;   /* counts number of total events */
int event_count_active; /* counts number of active events */
int event_gotterm;   /* Set to terminate loop */
int event_break;   /* Set to terminate loop immediately */
/* active event management */
struct event_list**activequeues;
int nactivequeues;
/* signal handling info */
struct evsignal_info sig;
struct event_list eventqueue;
struct timeval event_tv;
struct min_heap timeheap;
struct timeval tv_cache;
}; 
           

下面詳細解釋一下結構體中各字段的含義。

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*); // 删除事件
19
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中對應的接口函數主要就是:

int event_add(struct event*ev, const struct timeval*timeout);
int event_del(struct event*ev);
int event_base_loop(struct event_base*base, int loops);
void event_active(struct event*event, int res, short events);
void event_process_active(struct event_base*base);
本節将按介紹事件注冊和删除的代碼流程,libevent 的事件循環架構将在下一節再具體
描述。
對于定時事件,這些函數将調用timer heap管理接口執行插入和删除操作;對于I/O和
20
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細節之處的巧妙設計,且仔細看程式代碼,部分有省略,
注釋直接附在代碼中。
int event_add(struct event*ev, const struct timeval*tv)
{
struct event_base*base= ev->ev_base; // 要注冊到的event_base
const struct eventop*evsel= base->evsel;
void*evbase= base->evbase; // base使用的系統I/O政策
// 新的timer事件,調用timer heap接口在堆上預留一個位置
 // 注:這樣能保證該操作的原子性:
 // 向系統I/O機制注冊可能會失敗,而當在堆上預留成功後,
 // 定時事件的添加将肯定不會失敗;
 // 而預留位置的可能結果是堆擴充,但是内部元素并不會改變
if(tv!= NULL&& !(ev->ev_flags& EVLIST_TIMEOUT)) {
if(min_heap_reserve(&base->timeheap,
   1 + min_heap_size(&base->timeheap)) == -1)
return(-1); /* ENOMEM == errno */
}
// 如果事件ev不在已注冊或者激活連結清單中,則調用evbase注冊事件
if((ev->ev_events& (EV_READ|EV_WRITE|EV_SIGNAL)) &&
!(ev->ev_flags& (EVLIST_INSERTED|EVLIST_ACTIVE))) {
res= evsel->add(evbase, ev);
if(res!= -1) // 注冊成功,插入event到已注冊連結清單中
event_queue_insert(base, ev, EVLIST_INSERTED);
}
// 準備添加定時事件
if(res!= -1 && tv!= NULL) {
struct timeval now;
// EVLIST_TIMEOUT表明event已經在定時器堆中了,删除舊的
if(ev->ev_flags& EVLIST_TIMEOUT)
event_queue_remove(base, ev, EVLIST_TIMEOUT);
21
// 如果事件已經是就緒狀态則從激活連結清單中删除
if((ev->ev_flags& EVLIST_ACTIVE) &&
(ev->ev_res& EV_TIMEOUT)) {
// 将ev_callback調用次數設定為0
if(ev->ev_ncalls&& ev->ev_pncalls) {
*ev->ev_pncalls= 0;
}
event_queue_remove(base, ev, EVLIST_ACTIVE);
}
// 計算時間,并插入到timer小根堆中
gettime(base, &now);
evutil_timeradd(&now, tv, &ev->ev_timeout);
event_queue_insert(base, ev, EVLIST_TIMEOUT);
}
return(res);
}
event_queue_insert()負責将事件插入到對應的連結清單中,下面是程式代碼;
event_queue_remove()負責将事件從對應的連結清單中删除,這裡就不再重複貼代碼了;
void event_queue_insert(struct event_base*base, struct event*ev,
int queue)
{
// ev可能已經在激活清單中了,避免重複插入
if(ev->ev_flags& queue) {
if(queue& EVLIST_ACTIVE)
return;
}
// ...
ev->ev_flags|= queue; // 記錄queue标記
switch(queue) {
case EVLIST_INSERTED: // I/O或Signal事件,加入已注冊事件連結清單
TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
break;
case EVLIST_ACTIVE: // 就緒事件,加入激活連結清單
base->event_count_active++;
TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], ev,
ev_active_next);
break;
case EVLIST_TIMEOUT: // 定時事件,加入堆
min_heap_push(&base->timeheap, ev);
break;
}
} 
           

2)删除事件:

函數原型為:int event_del(struct event*ev);

該函數将删除事件ev,對于I/O事件,從I/O 的demultiplexer上将事件登出;對于Signal

事件,将從Signal事件連結清單中删除;對于定時事件,将從堆上删除;

同樣删除事件的操作則不一定是原子的,比如删除時間事件之後,有可能從系統I/O機

制中登出會失敗。

int event_del(struct event*ev)
{
struct event_base*base;
const struct eventop*evsel;
void*evbase;
// ev_base為NULL,表明ev沒有被注冊
if(ev->ev_base== NULL)
return(-1);
// 取得ev注冊的event_base和eventop指針
base= ev->ev_base;
evsel= base->evsel;
evbase= base->evbase;
// 将ev_callback調用次數設定為
if(ev->ev_ncalls&& ev->ev_pncalls) {
*ev->ev_pncalls= 0;
}
// 從對應的連結清單中删除
if(ev->ev_flags& EVLIST_TIMEOUT)
event_queue_remove(base, ev, EVLIST_TIMEOUT);
if(ev->ev_flags& EVLIST_ACTIVE)
event_queue_remove(base, ev, EVLIST_ACTIVE);
if(ev->ev_flags& EVLIST_INSERTED) {
event_queue_remove(base, ev, EVLIST_INSERTED);
// EVLIST_INSERTED表明是I/O或者Signal事件,
// 需要調用I/O demultiplexer登出事件
return(evsel->del(evbase, ev));
}
return(0);
}
           

繼續閱讀