本次分析的版本是libevent-2.0.21-stable
event_base是libevent的核心对象,它代表了libevent的事件处理框架。
往里跟event_base_new函数,可得:
由此可见,我们可以在分配event_base_new之前完成一些配置信息,从而改变事件处理框架的默认行为。
具体的函数有
event_config_avoid_method(),不使用某些事件处理框架,如select等
event_config_require_features(),//指定使用某些feature,如EV_FEATURE_ET
event_config_set_flag(),//指定flag,如EVENT_BASE_FLAG_NOLOCK
event_config_set_num_cpus_hint()在windows下IOCP时,试图使用的CPU数量
至此,libevent初始化完毕。看完了初始化,再来理解event_base就会相对容易一些。 它首先会选中一种事件处理框架,将之存放在evsel中。然后需要存放与该事件处理框架相关的对象,存入evbase中。
evbase在eventinternal.h 中定义。我就不贴代码了,注释挺详细的。
另外在这里可以看到libevent用到了tail queue(使用TAILQ_XXX来操作),哈希表(使用HT_XXX来操作 ),最小堆等数据结构。没见过tail queue,之前的算法书也没提过哈希表的实现。所以应该在下篇分析一下。
event_base是libevent的核心对象,它代表了libevent的事件处理框架。
在libevent的官方网站上,它号称支持/dev/poll, kqueue(2), event ports, POSIX select(2), Windows select(), poll(2), 跟 epoll(4) 。之所以有这么大的支持力度,因为libevent的事件处理框架并不直接调用系统API,而是为支持的操作系统封装了一系列的回调函数。
从它生成的Makefile可以看出来:
build_triplet = armv7l-unknown-linux-gnueabihf
host_triplet = armv7l-unknown-linux-gnueabihf
am__append_1 = libevent_pthreads.la
am__append_2 = libevent_pthreads.pc
#am__append_3 = libevent_openssl.la
#am__append_4 = libevent_openssl.pc
am__append_5 = select.c
am__append_6 = poll.c
#am__append_7 = devpoll.c
#am__append_8 = kqueue.c
am__append_9 = epoll.c
#am__append_10 = evport.c
am__append_11 = signal.c
#am__append_12 = $(EVENT1_HDRS)
.....
SYS_SRC = $(am__append_5) $(am__append_6) \
$(am__append_7) $(am__append_8) \
$(am__append_9) $(am__append_10) \
$(am__append_11)
由此可见它根据操作系统的支持程度不同,编入select.c, poll.c等。
而在event.c 中又有以下定义
static const struct eventop *eventops[] = {
#ifdef _EVENT_HAVE_EVENT_PORTS
evportops,
#endif
#ifdef _EVENT_HAVE_WORKING_KQUEUE
kqops,
#endif
#ifdef _EVENT_HAVE_EPOLL
epollops,
#endif
#ifdef _EVENT_HAVE_DEVPOLL
devpollops,
#endif
#ifdef _EVENT_HAVE_POLL
pollops,
#endif
#ifdef _EVENT_HAVE_SELECT
selectops,
#endif
#ifdef WIN32
win32ops,
#endif
NULL
};
由此可见,libevent支持哪些异步i/o调用是在编译期指定的。
现在开始进入代码。为了使用libevent, 创建一个libevent对象是必须的。我们一般调用的是:
struct event_base *base;
base = event_base_new();
往里跟event_base_new函数,可得:
struct event_base *
event_base_new(void)
{
struct event_base *base = NULL;
struct event_config *cfg = event_config_new();
if (cfg) {
base = event_base_new_with_config(cfg);
event_config_free(cfg);
}
return base;
}
由此可见,我们可以在分配event_base_new之前完成一些配置信息,从而改变事件处理框架的默认行为。
具体的函数有
event_config_avoid_method(),不使用某些事件处理框架,如select等
event_config_require_features(),//指定使用某些feature,如EV_FEATURE_ET
event_config_set_flag(),//指定flag,如EVENT_BASE_FLAG_NOLOCK
event_config_set_num_cpus_hint()在windows下IOCP时,试图使用的CPU数量
继续往里跟,可得以下代码:
event_base_new_with_config(const struct event_config *cfg)
{
...
mm_calloc(1, sizeof(struct event_base)))//分配内存
detect_monotonic();//检测是否支持monotonic
gettime(base, base->event_tv);
//一系列的初始化操作。清空time_heap,event_queue等等,不一一列举。
min_heap_ctor(base->timeheap);
TAILQ_INIT(base->eventqueue);
base->sig.ev_signal_pair[0] = -1;
base->sig.ev_signal_pair[1] = -1;
base->th_notify_fd[0] = -1;
base->th_notify_fd[1] = -1;
event_deferred_cb_queue_init(base->defer_queue);//
base->defer_queue.notify_fn = notify_base_cbq_callback;
base->defer_queue.notify_arg = base;
if (cfg)
base->flags = cfg->flags;
evmap_io_initmap(base->io);
evmap_signal_initmap(base->sigmap);
event_changelist_init(base->changelist);
base->evbase = NULL;
should_check_environment =
!(cfg (cfg->flags EVENT_BASE_FLAG_IGNORE_ENV));
//eventops是一个静态数组,存放着系统支持的异步框架(select,poll,epoll...)
for (i = 0; eventops[i] !base->evbase; i++) {
if (cfg != NULL) {
/* determine if this backend should be avoided */
if (event_config_is_avoided_method(cfg,
eventops[i]->name))
continue;
if ((eventops[i]->features cfg->require_features)
!= cfg->require_features)
continue;
}
/* also obey the environment variables */
if (should_check_environment
event_is_method_disabled(eventops[i]->name))
continue;
//选中一个事件处理框架,在这行代码可以看到虽然系统可能支持多种异步处理框架,但libevent只会选用一种。
//默认选中的是eventops数组里的最后一条,但是可以通过event_config_avoid_method,event_config_require_features
//来修改,选中我们中意的事件处理框架
base->evsel = eventops[i];
//调用该框架下的处理函数,例如epoll框架需要调用epoll_create来创建队列。
//返回一个框架操作对象,里面存放着相关的句柄信息。如epoll需要存放epoll_create返回的fd等等。
base->evbase = base->evsel->init(base);
}
//经过event_config_avoid_method,event_config_require_features的过滤后,有可能找不到中意的事件处理框架。
//这时返回NULL
if (base->evbase == NULL) {
event_warnx("%s: no event mechanism available",
__func__);
base->evsel = NULL;
event_base_free(base);
return NULL;
}
if (evutil_getenv("EVENT_SHOW_METHOD"))
event_msgx("libevent using: %s", base->evsel->name);
/* allocate a single active event queue */
if (event_base_priority_init(base, 1) < 0) {
event_base_free(base);
return NULL;
}
/* prepare for threading */
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
if (EVTHREAD_LOCKING_ENABLED() &&
(!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
int r;
EVTHREAD_ALLOC_LOCK(base->th_base_lock,
EVTHREAD_LOCKTYPE_RECURSIVE);
base->defer_queue.lock = base->th_base_lock;
EVTHREAD_ALLOC_COND(base->current_event_cond);
r = evthread_make_base_notifiable(base);
if (r<0 event_warnx="" s:="" unable="" to="" make="" base="" notifiable="" __func__="" event_base_free="" base="" return="" null="" endif="" ifdef="" win32="" if="" cfg="" cfg-="">flags & EVENT_BASE_FLAG_STARTUP_IOCP))
event_base_start_iocp(base, cfg->n_cpus_hint);
#endif
return (base);
}
至此,libevent初始化完毕。看完了初始化,再来理解event_base就会相对容易一些。 它首先会选中一种事件处理框架,将之存放在evsel中。然后需要存放与该事件处理框架相关的对象,存入evbase中。
evbase在eventinternal.h 中定义。我就不贴代码了,注释挺详细的。
另外在这里可以看到libevent用到了tail queue(使用TAILQ_XXX来操作),哈希表(使用HT_XXX来操作 ),最小堆等数据结构。没见过tail queue,之前的算法书也没提过哈希表的实现。所以应该在下篇分析一下。