文章目錄
一、從signal-test.c示例代碼開始
原本準備直接剖析Github上最新版本的libevent源碼,但下載下來之後就…,於是決定先從頗爲養眼的libevent-1.4代碼開始剖析,從libevent-patches-1.4\sample\signal-test.c開始:
/* Compile with:
* cc -I/usr/local/include -o signal-test \
* signal-test.c -L/usr/local/lib -levent*/
int called = 0;
static void signal_cb(int fd, short event, void *arg){...}
int main (int argc, char **argv)
{
struct event signal_int;
/* Initalize the event library */
struct event_base* base = event_base_new();
/* Initalize one event */
event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb,
&signal_int);
event_base_set(base, &signal_int);
event_add(&signal_int, NULL);
event_base_dispatch(base);
event_base_free(base);
return (0);
}
可以看出函數先調用event_base_new()函數初始化event_base結構體:
struct evsignal_info {
struct event ev_signal;
int ev_signal_pair[2];
...
};
struct event_base {
//libevent-patches-1.4\event-internal.h
const struct eventop *evsel;
void *evbase; /*事件分發reactor機制的數據結構*/
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;//tv_cache是時間緩存
};
二、由event_base_new函數引發的解析
struct event_base *event_base_new(void)
{//libevent-patches-1.4\event.c
struct event_base *base;
if ((base = calloc(1, sizeof(struct event_base))) == NULL)
...
//event_sigcb,event_gotsig爲兩個全局變量
event_sigcb = NULL; /*函數指針, Signal callback when gotsig is set */
event_gotsig = 0; /* Set in signal handler */
...
/*這些代碼初始化了event_base中的event_tv,timeheap,eventqueue,
sig變量,具體代碼此處不表,變量作用已於結構體定義處註釋*/
...
base->evbase = NULL;
for (i = 0; eventops[i] && !base->evbase; i++) {
base->evsel = eventops[i];
base->evbase = base->evsel->init(base);
}
...
/* allocate a single active event queue */
event_base_priority_init(base, 1);
return (base);
}
[1]對base->evsel與base->evbase的初始化剖析
event_base_new函數先爲base分配內存,根據註釋之後又初始化了幾個變量,然後重點關注對base->evsel(const struct eventop *)與base->evbase(void *)的初始化工作。顯然evsel是一個struct eventop結構體,它的定義如下:
struct eventop {
//libevent-patches-1.4\event-internal.h
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;
};
在這段代碼中有一個全局變量eventops,找到其定義位置得到:
/* In order of preference */
static const struct eventop *eventops[] = {
... //libevent-patches-1.4\event.c
&evportops, ...
&kqops, ...
&epollops, ...
&devpollops,...
&pollops, ...
&selectops, ...
&win32ops, ... /*...表示諸多判斷語句*/
NULL
};
看到這兒就很清楚了,顯然eventops是一個描述多路複用IO技術的數組,其中每一項是一個多路複用IO技術實例,即struct eventop本質應是爲了描述多路複用技術的, 並且其按照性能優先級排序,由編譯器根據是否定義相關數據結構確定每一項的有效性,在event_base_new函數中默認選取性能最高的,也即優先級最高的一項技術。接下來函數會調用該技術的init函數初始化base的evbase成員。接下來就以全局變量eventops的pollops爲例來看一看具體執行邏輯:
//libevent-patches-1.4\poll.c
const struct eventop pollops = {
"poll", poll_init, poll_add, poll_del,
poll_dispatch, poll_dealloc, 0
};
static void *poll_init(struct event_base *base)
{
struct pollop *pollop;
...
if (!(pollop = calloc(1, sizeof(struct pollop))))
return (NULL);
evsignal_init(base);
return (pollop);
}
顯然poll_init函數先爲struct pollop分配內存,然後調用evsignal_init函數,那麼這個函數
是要幹啥哩?
int evsignal_init(struct event_base *base)
{//libevent-patches-1.4\signal.c
int i;
if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1)
...
FD_CLOSEONEXEC(base->sig.ev_signal_pair[0]);
FD_CLOSEONEXEC(base->sig.ev_signal_pair[1]);
base->sig.sh_old_max = 0;
base->sig.evsignal_caught = 0;
/*期間省略種種皆是爲了初始化base->sig變量*/
event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1], \
EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal);
/*上面將sockpair的讀端sig.ev_signal_pair[1]構造爲一個基本的event事件,可
用於後面通知信號來了, evsignal_cb爲其回調函數*/
...
}
顯然這個函數發揮的作用和名字一樣,它初始化了event_base的sig成員變量。期間有兩點需要注意一下,其一是evutil_socketpair()函數,它在底層會調用socketpair函數生成一對socket隊組用以初始化sig.ev_signal_pair成員:
int evutil_socketpair(int family, int type, int protocol, int fd[2]) {
#ifndef WIN32
return socketpair(family, type, protocol, fd);
#else
其二是宏FD_CLOSEONEXEC(x),其底層實現是調用fcntl函數設置文件描述符x爲FD_CLOEXEC,以實現在調用exec族函數時關閉子進程無用文件描述符。
#define FD_CLOSEONEXEC(x) do { if (fcntl(x, F_SETFD, 1) == -1) \
event_warn("fcntl(%d, F_SETFD)", x); \
} while (0)
最後poll_init函數返回表徵其自身的結構體struct pollop作爲event_base->evbase成員:
struct pollop {
int event_count; /* Highest number alloc */
int nfds; /* Size of event_* */
int fd_count; /* Size of idxplus1_by_fd */
struct pollfd *event_set;
struct event **event_r_back;
struct event **event_w_back;
int *idxplus1_by_fd;
};
[2]對event_base_priority_init函數的剖析
#define TAILQ_INIT(head) do { \
(head)->tqh_first = NULL; \
(head)->tqh_last = &(head)->tqh_first; \
} while (0)
int event_base_priority_init(struct event_base *base, int npriorities)
{//libevent-patches-1.4\event.c
... /*校驗輸入參數的合法性*/
/* Allocate our priority queues */
base->nactivequeues = npriorities;
base->activequeues = (struct event_list **)calloc(base->nactivequeues, sizeof(struct event_list *));
...
for (i = 0; i < base->nactivequeues; ++i) {
base->activequeues[i] = malloc(sizeof(struct event_list));
...
TAILQ_INIT(base->activequeues[i]);
}
...
該函數初始化了base->nactivequeues與base->activequeues,至此event_base_new函數函數結束返回一個動態分配並初始化的struct event_base對象供主線程使用。
三、初始化signal_int引發的剖析
接下來先調用event_set函數初始化一個struct event結構體。
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
struct event {
TAILQ_ENTRY (event) ev_next;
TAILQ_ENTRY (event) ev_active_next;
TAILQ_ENTRY (event) ev_signal_next;
unsigned int min_heap_idx; /* for managing timeouts */
struct event_base *ev_base;
int ev_fd;
short ev_events;
short ev_ncalls;
short *ev_pncalls; /* Allows deletes in callback */
struct timeval ev_timeout;
int ev_pri; /* smaller numbers are higher priority */
void (*ev_callback)(int, short, void *arg);
void *ev_arg;
int ev_res; /* result passed to event callback */
int ev_flags;
};
[1]event_set函數
#define SIGINT 2 /* interrupt */
#define EV_SIGNAL 0x08
#define EV_PERSIST 0x10 /* Persistant event */
#define EVLIST_INIT 0x80
struct event_base *current_base = NULL;//libevent-patches-1.4\event.c
調用event_set(&signal_int, SIGINT, EV_SIGNAL|EV_PERSIST, signal_cb,&signal_int);
void event_set(struct event *ev, int fd, short events, \
void (*callback)(int, short, void *), void *arg)
{
/* Take the current base - caller needs to set the real base later */
ev->ev_base = current_base;
ev->ev_callback = callback;
ev->ev_arg = arg;
ev->ev_fd = fd;
ev->ev_events = events;
ev->ev_flags = EVLIST_INIT;
.../*初始化各項成員*/
[2]event_base_set函數
調用event_base_set(base, &signal_int);
,將signal_int的ev_base設爲base
int event_base_set(struct event_base *base, struct event *ev)
{
/* Only innocent events may be assigned to a different base */
if (ev->ev_flags != EVLIST_INIT)
return (-1);
ev->ev_base = base;
ev->ev_pri = base->nactivequeues/2;
return (0);
}
[3]event_add函數
調用event_add(&signal_int, NULL);
int event_add(struct event *ev, const struct timeval *tv)
{
struct event_base *base = ev->ev_base;
const struct eventop *evsel = base->evsel;
void *evbase = base->evbase;
int res = 0;
...
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_queue_insert(base, ev, EVLIST_INSERTED);
}
...
顯然event_add函數會檢查當前事件是否是讀/寫/信號響應事件以及事件的標誌,若檢查合法則調用前面選擇的多路事件技術的add操作,如調用poll_add:
static int poll_add(void *arg, struct event *ev)
{
struct pollop *pop = arg;
struct pollfd *pfd = NULL;
int i;
if (ev->ev_events & EV_SIGNAL)
return (evsignal_add(ev));
.../*剩餘部分當爲其他類型事件所設計*/
因爲本程序爲監聽信號中斷事件,故此處將繼續剖析evsignal_add函數:
int evsignal_add(struct event *ev)
{
int evsignal;
struct event_base *base = ev->ev_base;
struct evsignal_info *sig = &ev->ev_base->sig;
...
evsignal = EVENT_SIGNAL(ev);//得ev->ev_fd;
assert(evsignal >= 0 && evsignal < NSIG);
if (TAILQ_EMPTY(&sig->evsigevents[evsignal])) {
/*TAILQ_EMPTY宏判斷該信號是否註冊過,若未則執行以下邏輯註冊之*/
if (_evsignal_set_handler(base, evsignal, evsignal_handler) == -1)
return (-1);/*此函數內註冊調用signal/sigaction函數註冊之*/
.../*此處註冊本地套,不做剖析*/
}
/*下面這個宏用於設置sig->evsigevents[evsignal],這樣下次已註冊信號若來了
則不會繼續執行上面的條件語句了*/
TAILQ_INSERT_TAIL(&sig->evsigevents[evsignal], ev, ev_signal_next);
return (0);
}
該函數主要用於將該信號註冊在內核裏面,由上面的註釋可以清楚的看出,它藉助signal與sigaction函數達到最終目的。這個函數應該也是event_add函數在註冊信號這方面的主要處理流程。接下里觀察當內核收到信號之後就會調用回調函數evsignal_handler,這不是用戶層給出的signal_cb函數,至於何時會調用,現在我也不知道,想來應該是下面喚醒sockpair的讀端之後,調用evsignal_cb函數先處理讀事件然後再處理信號捕獲標記和計數,從而執行原本註冊的回調函數signal_cb吧,後面會驗證這個猜想的準確性。先來看一看evsignal_handler回調函數:
static void evsignal_handler(int sig)
{
...
evsignal_base->sig.evsigcaught[sig]++;
evsignal_base->sig.evsignal_caught = 1;
/*上面表明已經捕獲到信號,並且使用了信號進行計數*/
#ifndef HAVE_SIGACTION
signal(sig, evsignal_handler);
#endif
/* Wake up our notification mechanism */
send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
errno = save_errno;
}
四、事件分發:event_base_dispatch
調用event_base_dispatch(base),該函數又會調用event_base_loop(base, 0):
int event_base_loop(struct event_base *base, int flags)
{
const struct eventop *evsel = base->evsel;
void *evbase = base->evbase;
struct timeval tv;
struct timeval *tv_p;
int res, done;
...
done = 0;
while (!done) {
/* Terminate the loop if we have been asked to */
...
/* You cannot use this interface for multi-threaded apps */
while (event_gotsig) {
event_gotsig = 0;
if (event_sigcb) {
res = (*event_sigcb)();
if (res == -1) {
errno = EINTR;
return (-1);
}
}
}
...
res = evsel->dispatch(base, evbase, tv_p);
...
if (base->event_count_active) {
event_process_active(base);
if (!base->event_count_active && (flags & EVLOOP_ONCE))
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
...
}
[1]evsel->dispatch(base, evbase, tv_p);
可以看到event_base_loop基本上就是一個while循環,不斷的處理信號事件、定時事件以及監聽事件,從程序流程來看,信號、定時以及監聽事件分別獨立處理。此處忽略定時等一系列操作,先着重關注evsel->dispatch操作,具體仍舊以poll爲例:
static int poll_dispatch(struct event_base *base, void *arg, struct timeval *tv)
{
struct pollop *pop = arg
... /*檢查及初始化工作*/
nfds = pop->nfds;
res = poll(pop->event_set, nfds, msec);
if (res == -1) {
if (errno != EINTR) { ...
return (-1);
}
evsignal_process(base);
return (0);//此時發生中斷,重新來過
} else if (base->sig.evsignal_caught) {
evsignal_process(base);//被正常註冊的信號喚醒了
}
... /*校驗res,若有其他類型事件處理之*/
}
顯然若捕捉到信號則調用evsignal_process(base)函數處理該信號,下面看一看該函數:
/*NSIG是一個定義的宏,它描述了定義的信號的數量。
由於信號的數值是從0開始連續分配的,所以,NSIG比系統中所定義的最大的信號數值大1。*/
/*struct event_list define:*/
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
}
TAILQ_HEAD (event_list, event);
/*故marco展開爲:*/
struct event_list { \
struct event *tqh_first; /* first element */ \
struct event **tqh_last; /* addr of last next element */ \
};
void evsignal_process(struct event_base *base)
{
struct evsignal_info *sig = &base->sig;
struct event *ev, *next_ev;
sig_atomic_t ncalls;
...
base->sig.evsignal_caught = 0;
for (i = 1; i < NSIG; ++i) {
...
for (ev = TAILQ_FIRST(&sig->evsigevents[i]);ev != NULL; ev = next_ev) {
next_ev = TAILQ_NEXT(ev, ev_signal_next);
...
event_active(ev, EV_SIGNAL, ncalls);
}
...
}
該函數顯然是通過一個for循環得到一個信號出現的次數,然後先調用event_active(ev, EV_SIGNAL, ncalls)—>event_queue_insert(ev->ev_base, ev, EVLIST_ACTIVE),在這個函數裏面調用TAILQ_INSERT_TAIL宏將該信號事件掛在event_base上的
struct event_list **activequeues隊列上,以便後面處理時方便找到。
void event_queue_insert(struct event_base *base, struct event *ev, int queue){
... /*switch (queue)*/
case EVLIST_ACTIVE:
base->event_count_active++;
TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri],
ev,ev_active_next);
break;
...
回顧函數調用流程可知,依次爲event_base_dispatch(base)–>event_base_loop(base, 0)–>evsel->dispatch(base, evbase, tv_p)–>poll()–>evsignal_process(base)–>event_active(ev, EV_SIGNAL, ncalls)—>event_queue_insert(ev->ev_base, ev, EVLIST_ACTIVE)該函數將信號事件掛在event_base上的 struct event_list **activequeues隊列上,至此poll_dispatch函數告一段落。接下來看一看event_base_loop函數的下一部分:
[2]event_process_active函數
由上面的event_queue_insert()函數可知每加入一個事件base->event_count_active++,故此處需要調用event_process_active(base)函數處理之:
static void
event_process_active(struct event_base *base)
{
struct event *ev;
struct event_list *activeq = NULL;
... /*開始獲取發生事件隊列*/
activeq = base->activequeues[i];
...
for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
if (ev->ev_events & EV_PERSIST)
event_queue_remove(base, ev, EVLIST_ACTIVE);
.../*這個條件判斷if-else語句將要處理事件
從待處理事件隊列base->activequeues中刪除*/
...
while (ncalls) {
ncalls--;
ev->ev_ncalls = ncalls;
(*ev->ev_callback)((int)ev->ev_fd, ev->ev_res, ev->ev_arg);
...
}
顯然該函數先將事件從base->activequeues[i]待處理事件隊列中刪除之後,再根據該事件觸發的次數調用應用層給出的回調函數,再本函數中應當調用signal_cb函數。
五、空間釋放:event_base_free
void event_base_free(struct event_base *base)
{
struct event *ev;
/*還原全局變量*/
if (base == NULL && current_base)
base = current_base;
if (base == current_base)
current_base = NULL;
。。。
/*清除base->eventqueue*/
for (ev = TAILQ_FIRST(&base->eventqueue); ev; ) {...}
/*清除base->timeheap*/
while ((ev = min_heap_top(&base->timeheap)) != NULL) {...}
/*清除base->activequeues上掛的events*/
for (i = 0; i < base->nactivequeues; ++i) {...}
...
/*調用poll_dealloc,歸還poll資源*/
base->evsel->dealloc(base, base->evbase);
.../*清除base->activequeues*/
for (i = 0; i < base->nactivequeues; ++i)
free(base->activequeues[i]);
free(base->activequeues);...
free(base);
}