1. 程式人生 > >Libevent原始碼分析-----與event相關的一些函式和操作

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通知主執行緒》。