libevent原始碼分析--事件處理框架
1 事件處理框架-event_base
回想Reactor模式的幾個基本元件,本節講解的部分對應於Reactor框架元件。在libevent中,這就表現為event_base結構體,結構體宣告如下,它位於event-internal.h檔案中:
- struct event_base {
- conststruct 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
- 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 *); // 刪除事件
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, conststruct 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和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, conststruct timeval *tv)
- {
- struct event_base *base = ev->ev_base; // 要註冊到的event_base
- conststruct 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);
- // 如果事件已經是就緒狀態則從啟用連結串列中刪除
- 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;
- conststruct 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);
- }
4 小節
分析了event_base這一重要結構體,初步看到了libevent對系統的I/O demultiplex機制的封裝event_op結構,並結合原始碼分析了事件的註冊和刪除處理,下面將會接著分析事件管理框架中的主事件迴圈部分。