Libevent原始碼分析-----與event相關的一些函式和操作
Libevent提供了一些與event相關的操作函式和操作。本文就重點講一下這方面的原始碼。
在Libevent中,無論是event還是event_base,都是使用指標而不會使用變數。實際上,如果檢視Libevent不同的版本,就可以發現event和event_base這兩個結構體的成員是不同的。對比libevent-2.0.21-stable和libevent-1.4.13-stable這兩個版本,就可以發現其具有相當大的區別。
event的引數:
一個event結構體和很多東西相關聯,比如event_base、檔案描述符fd、回撥函式、回撥引數等等,下文把這些東西統一稱為引數。這些引數都是在呼叫event_new建立一個event時指定的。如果在後面需要再次獲取這些引數時,可以通過一些函式來獲取,而不應該直接訪問event結構體的成員。
//event.c檔案 evutil_socket_t //監聽的檔案描述符fd event_get_fd(const struct event *ev) { return ev->ev_fd; } struct event_base * //獲取event_base event_get_base(const struct event *ev) { return ev->ev_base; } short //獲取該event監聽的事件 event_get_events(const struct event *ev) { return ev->ev_events; } event_callback_fn //獲取回撥函式的函式指標 event_get_callback(const struct event *ev) { return ev->ev_callback; } void * //獲取回撥函式引數 event_get_callback_arg(const struct event *ev) { return ev->ev_arg; } void //一個函式獲取所有 event_get_assignment(const struct event *event, struct event_base **base_out, evutil_socket_t *fd_out, short *events_out, event_callback_fn *callback_out, void **arg_out) { if (base_out) *base_out = event->ev_base; if (fd_out) *fd_out = event->ev_fd; if (events_out) *events_out = event->ev_events; if (callback_out) *callback_out = event->ev_callback; if (arg_out) *arg_out = event->ev_arg; }
前面的那些函式是獲取單個引數的,最後那個函式可以同時獲取多個引數。並且如果不想獲取某個引數,可以對應地傳入一個NULL。
event的狀態:
一個event是可以有多個狀態的,比如已初始化狀態(initialized)、未決狀態(pending)、啟用狀態(active)。
可以用event_initialized函式檢測一個event是否處於已初始化狀態:
//event.c檔案 int event_initialized(const struct event *ev) { if (!(ev->ev_flags & EVLIST_INIT)) return 0; return 1; }
可以看到event_initialized只是檢查event的ev_flags是否有EVLIST_INIT標誌。從之前的博文可以知道,當用戶呼叫event_new後,就會為event加入該標誌,所以用event_new建立的even都是處於已初始化狀態的。
當用戶呼叫event_new建立一個event後,它還沒處於未決狀態(non-pending),當用戶呼叫event_add函式,將一個event插入到event_base佇列後,就處於未決狀態(pending)。
如果event監聽的事件發生了或者超時了,那麼該event就會被啟用,處於啟用狀態。當event的回撥函式被呼叫後,它就不再是啟用狀態了,但還是處於未決狀態。如果使用者呼叫了event_del或者event_free(該函式內部呼叫event_del),那麼該event就不再是未決狀態了。
可以呼叫event_pending函式來檢查event處於哪種事件的未決狀態。但是該函式不僅僅會檢查event的未決狀態,還會檢查event的啟用狀態。名不副實啊!!下面就看一下這個函式吧。
//event.c檔案
int
event_pending(const struct event *ev, short event, struct timeval *tv)
{
int flags = 0;
if (EVUTIL_FAILURE_CHECK(ev->ev_base == NULL)) {
event_warnx("%s: event has no event_base set.", __func__);
return 0;
}
EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
//flags記錄使用者監聽了哪些事件
if (ev->ev_flags & EVLIST_INSERTED)
flags |= (ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL));
//flags記錄event被什麼事件激活了.使用者可以呼叫event_active
//手動啟用event,並且可以使用之前使用者沒有監聽的事件作為啟用原因
if (ev->ev_flags & EVLIST_ACTIVE)
flags |= ev->ev_res;
//記錄該event是否還有超時屬性
if (ev->ev_flags & EVLIST_TIMEOUT)
flags |= EV_TIMEOUT;
//event可以被使用者亂設值,然後作為引數。這裡為了保證
//其值只能是下面的事件。
event &= (EV_TIMEOUT|EV_READ|EV_WRITE|EV_SIGNAL);
/* See if there is a timeout that we should report */
if (tv != NULL && (flags & event & EV_TIMEOUT)) {
struct timeval tmp = ev->ev_timeout;
tmp.tv_usec &= MICROSECONDS_MASK;
#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
/* correctly remamp to real time */
evutil_timeradd(&ev->ev_base->tv_clock_diff, &tmp, tv);
#else
*tv = tmp;
#endif
}
EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
return (flags & event);
}
該函式的作用是檢查某個事件(由第二個引數指定)是否處於未決或者啟用狀態。
由flags的幾個 |= 操作可知,它會把event監聽的事件種類都記錄下來。並且還會把event被啟用的原因(也是一個事件)記錄下來。下面會講到手動啟用一個event。所以event可能會被一個沒有監聽的事件鎖啟用。
如果該函式的第三個引數不為NULL,並且使用者之前也讓這個event監聽了超時事件,而且使用者在第二個引數中指明瞭要檢查超時事件,那麼將第三個引數將被賦值為該event的下次超時時間(絕對時間)。
event_pending函式的一個作用是可以判斷一個event是否已經從event_base中刪除了。比如說,某個event監聽寫事件而加入了event_base,但可能在某個時刻被刪除。那麼可以用下面的程式碼判斷這個event是否已經被刪除了。
if( event_pending(ev, EV_WRITE, NULL) == 0 )
printf("delete\n");
else
printf("no delete\n");
手動啟用event:
除了執行event_base_dispatch死等外界條件把event啟用外,Libevent還提供了一個API函式event_active,可以手動地把一個event啟用。
//event.c檔案
//res是啟用的原因,是諸如EV_READ EV_TIMEOUT之類的巨集.
//ncalls只對EV_SIGNAL訊號有用,表示訊號的次數
//因為IO事件不講究次數,訊號才講究次數
void
event_active(struct event *ev, int res, short ncalls)
{
//加鎖,可以執行緒安全地手動啟用一個event
EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
event_active_nolock(ev, res, ncalls);
EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
}
void
event_active_nolock(struct event *ev, int res, short ncalls)
{
struct event_base *base;
//該event已經是啟用狀態
if (ev->ev_flags & EVLIST_ACTIVE) {
ev->ev_res |= res;
return;
}
base = ev->ev_base;
ev->ev_res = res;//記錄被啟用原因。以後會用到
...
if (ev->ev_events & EV_SIGNAL) {
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
if (base->current_event == ev && !EVBASE_IN_THREAD(base)) {
++base->current_event_waiters;
EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock);
}
#endif
ev->ev_ncalls = ncalls;
ev->ev_pncalls = NULL;
}
//將event插入到啟用佇列
event_queue_insert(base, ev, EVLIST_ACTIVE);
//呼叫本函式的執行緒不是主執行緒的話,就會通知主執行緒。使得主執行緒能趕快處理啟用event
if (EVBASE_NEED_NOTIFY(base))
evthread_notify_base(base);
}
手動啟用一個event的原理是:把event插入到啟用佇列。如果執行啟用動作的執行緒不是主執行緒,那麼還要喚醒主執行緒,讓主執行緒及時處理啟用event,不再睡眠在多路IO複用函式中。
由於手動啟用一個event是直接把這個event插入到啟用佇列的,所以event的被啟用原因(由res引數所指定)可以不是該event監聽的事件。比如說該event只監聽了EV_READ事件,那麼可以呼叫event_active(ev,EV_SIGNAL, 1);用訊號事件啟用該event。
刪除event:
之前的博文都只是講怎麼建立event和將之add到event_base中。現在來講一下怎麼刪除一個event。
void
event_free(struct event *ev)
{
event_del(ev);
mm_free(ev);//釋放記憶體
}
int
event_del(struct event *ev)
{
int res;
//加鎖保證執行緒安全
EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
res = event_del_internal(ev);
EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
return (res);
}
static inline int
event_del_internal(struct event *ev)
{
struct event_base *base;
int res = 0, notify = 0;
base = ev->ev_base;
/* See if we are just active executing this event in a loop */
if (ev->ev_events & EV_SIGNAL) {
if (ev->ev_ncalls && ev->ev_pncalls) {
/* Abort loop *///終止迴圈
*ev->ev_pncalls = 0;
}
}
//從超時集合中刪除.超時集合可能是小根堆也可能是common-timeout
if (ev->ev_flags & EVLIST_TIMEOUT) {
//刪除超時event並不需要通知主執行緒。如果該event不是最早超時的,
//那肯定不用通知了。如果是的話,那麼主執行緒會醒來。醒來後,
//主執行緒還是會再次檢查超時集合中有哪些超時event超時了。這個被
//刪除的超時event自然也檢查不出來。主執行緒只會空手而回。
event_queue_remove(base, ev, EVLIST_TIMEOUT);
}
//該event已經在active佇列中了。那麼需要在active佇列中刪除之
if (ev->ev_flags & EVLIST_ACTIVE)
event_queue_remove(base, ev, EVLIST_ACTIVE);
//該event已經在註冊佇列(eventqueue)中了,那麼需要在註冊佇列中刪除之
if (ev->ev_flags & EVLIST_INSERTED) {
event_queue_remove(base, ev, EVLIST_INSERTED);
//此外還要在該fd或者sig佇列中刪除之。同一個fd可以有多個event。
//所以這裡還有一個佇列
if (ev->ev_events & (EV_READ|EV_WRITE))
res = evmap_io_del(base, ev->ev_fd, ev);
else
res = evmap_signal_del(base, (int)ev->ev_fd, ev);
if (res == 1) {
/* evmap says we need to notify the main thread. */
notify = 1;
res = 0;
}
}
//可能需要通知主執行緒
if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
evthread_notify_base(base);
return (res);
}
雖然要呼叫三個函式才能刪除一個event,不過思路還是挺清晰的。刪除的時候要加鎖,刪除完後要釋放記憶體。從之前的博文也可以知道,一個event是會被加入到各種佇列中的。所以將一個event刪除,所做的工作主要是:將這個event從各種佇列中刪除掉。
刪除一個event這個操作可能不是主執行緒呼叫的,這時就可能需要通知主執行緒。關於通知主執行緒的原理可以參考博文《evthread_notify_base通知主執行緒》。