libevent源碼深度剖析六
——初見事件處理架構
張亮
前面已經對libevent的事件處理架構和event結構體做了描述,現在是時候剖析libevent對事件的詳細處理流程了,本節將分析libevent的事件處理架構event_base和libevent註冊、刪除事件的具體流程,可結合前一節libevent對event的管理。
1 事件處理架構-event_base
回想Reactor模式的幾個基本組件,本節講解的部分對應於Reactor架構組件。在libevent中,這就表現為event_base結構體,結構體聲明如下,它位於event-internal.h檔案中:
struct event_base {<br /> const struct eventop *evsel;<br /> void *evbase; <br /> int event_count; /* counts number of total events */<br /> int event_count_active; /* counts number of active events */<br /> int event_gotterm; /* Set to terminate loop */<br /> int event_break; /* Set to terminate loop immediately */<br /> /* active event management */<br /> struct event_list **activequeues;<br /> int nactivequeues;<br /> /* signal handling info */<br /> struct evsignal_info sig;<br /> struct event_list eventqueue;<br /> struct timeval event_tv;<br /> struct min_heap timeheap;<br /> struct timeval tv_cache;<br />};<br />
下面詳細解釋一下結構體中各欄位的含義。
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中對應的介面函數主要就是:
int event_add(struct event *ev, const struct timeval *timeout);<br />int event_del(struct event *ev);<br />int event_base_loop(struct event_base *base, int loops);<br />void event_active(struct event *event, int res, short events);<br />void event_process_active(struct event_base *base);<br />
本節將按介紹事件註冊和刪除的代碼流程,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細節之處的巧妙設計,且仔細看程式碼,部分有省略,注釋直接附在代碼中。
int event_add(struct event *ev, const struct timeval *tv)<br />{<br /> struct event_base *base = ev->ev_base; // 要註冊到的event_base<br /> const struct eventop *evsel = base->evsel;<br /> void *evbase = base->evbase; // base使用的系統I/O策略<br /> // 新的timer事件,調用timer heap介面在堆上預留一個位置<br /> // 註:這樣能保證該操作的原子性:<br /> // 向系統I/O機制註冊可能會失敗,而當在堆上預留成功後,<br /> // 定時事件的添加將肯定不會失敗;<br /> // 而預留位置的可能結果是堆擴充,但是內部元素並不會改變<br /> if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {<br /> if (min_heap_reserve(&base->timeheap,<br /> 1 + min_heap_size(&base->timeheap)) == -1)<br /> return (-1); /* ENOMEM == errno */<br /> }<br /> // 如果事件ev不在登入或者啟用鏈表中,則調用evbase註冊事件<br /> if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&<br /> !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {<br /> res = evsel->add(evbase, ev);<br /> if (res != -1) // 註冊成功,插入event到登入鏈表中<br /> event_queue_insert(base, ev, EVLIST_INSERTED);<br /> }<br /> // 準備添加定時事件<br /> if (res != -1 && tv != NULL) {<br /> struct timeval now;<br /> // EVLIST_TIMEOUT表明event已經在定時器堆中了,刪除舊的<br /> if (ev->ev_flags & EVLIST_TIMEOUT)<br /> event_queue_remove(base, ev, EVLIST_TIMEOUT);<br /> // 如果事件已經是就緒狀態則從啟用鏈表中刪除<br /> if ((ev->ev_flags & EVLIST_ACTIVE) &&<br /> (ev->ev_res & EV_TIMEOUT)) {<br /> // 將ev_callback調用次數設定為0<br /> if (ev->ev_ncalls && ev->ev_pncalls) {<br /> *ev->ev_pncalls = 0;<br /> }<br /> event_queue_remove(base, ev, EVLIST_ACTIVE);<br /> }<br /> // 計算時間,並插入到timer小根堆中<br /> gettime(base, &now);<br /> evutil_timeradd(&now, tv, &ev->ev_timeout);<br /> event_queue_insert(base, ev, EVLIST_TIMEOUT);<br /> }<br /> return (res);<br />}</p><p>event_queue_insert()負責將事件插入到對應的鏈表中,下面是程式碼;<br />event_queue_remove()負責將事件從對應的鏈表中刪除,這裡就不再重複貼代碼了;<br />void event_queue_insert(struct event_base *base, struct event *ev, int queue)<br />{<br /> // ev可能已經在啟用列表中了,避免重複插入<br /> if (ev->ev_flags & queue) {<br /> if (queue & EVLIST_ACTIVE)<br /> return;<br /> }<br /> // ...<br /> ev->ev_flags |= queue; // 記錄queue標記<br /> switch (queue) {<br /> case EVLIST_INSERTED: // I/O或Signal事件,加入登入事件鏈表<br /> TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);<br /> break;<br /> case EVLIST_ACTIVE: // 就緒事件,加入啟用鏈表<br /> base->event_count_active++;<br /> TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], ev, ev_active_next);<br /> break;<br /> case EVLIST_TIMEOUT: // 定時事件,加入堆<br /> min_heap_push(&base->timeheap, ev);<br /> break;<br /> }<br />}<br />
2)刪除事件:
函數原型為:int event_del(struct event *ev);
該函數將刪除事件ev,對於I/O事件,從I/O 的demultiplexer上將事件登出;對於Signal事件,將從Signal事件鏈表中刪除;對於定時事件,將從堆上刪除;
同樣刪除事件的操作則不一定是原子的,比如刪除時間事件之後,有可能從系統I/O機制中登出會失敗。
int event_del(struct event *ev)<br />{<br /> struct event_base *base;<br /> const struct eventop *evsel;<br /> void *evbase;<br /> // ev_base為NULL,表明ev沒有被註冊<br /> if (ev->ev_base == NULL)<br /> return (-1);<br /> // 取得ev註冊的event_base和eventop指標<br /> base = ev->ev_base;<br /> evsel = base->evsel;<br /> evbase = base->evbase;<br /> // 將ev_callback調用次數設定為<br /> if (ev->ev_ncalls && ev->ev_pncalls) {<br /> *ev->ev_pncalls = 0;<br /> }</p><p> // 從對應的鏈表中刪除<br /> if (ev->ev_flags & EVLIST_TIMEOUT)<br /> event_queue_remove(base, ev, EVLIST_TIMEOUT);<br /> if (ev->ev_flags & EVLIST_ACTIVE)<br /> event_queue_remove(base, ev, EVLIST_ACTIVE);<br /> if (ev->ev_flags & EVLIST_INSERTED) {<br /> event_queue_remove(base, ev, EVLIST_INSERTED);<br /> // EVLIST_INSERTED表明是I/O或者Signal事件,<br /> // 需要調用I/O demultiplexer登出事件<br /> return (evsel->del(evbase, ev));<br /> }<br /> return (0);<br />}<br />
4 小節
分析了event_base這一重要結構體,初步看到了libevent對系統的I/O demultiplex機制的封裝event_op結構,並結合原始碼分析了事件的註冊和刪除處理,下面將會接著分析事件管理架構中的主事件迴圈部分。