文章目录
一、从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);
}