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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章