libevent源碼分析(一)

一、從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);
}

下一篇

發佈了39 篇原創文章 · 獲贊 7 · 訪問量 4401
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章