###一.前言
本文将重点介绍event_base相关的几个接口函数,包括:
* event_base_new(), event_base_loop(),
* event_base_new_with_config()
###二.函数介绍
(1)event_base_new()函数
该函数功能如字面意思,即建立一个新的event_base,用于存放、管理事件。具体代码如下所示:
/*创建event_base*/
struct event_base *
event_base_new(void)
{
struct event_base *base = NULL;
/*创建一个event_config结构*/
struct event_config *cfg = event_config_new();
/*用event_config为event_base赋值*/
if (cfg) {
base = event_base_new_with_config(cfg);
event_config_free(cfg);
}
return base;
}
由代码可见,这里主要用到了一个event_config结构体以及下来要介绍的event_base_new_with_config()函数进行event_base的建立,所以这些放在event_base_new_with_config()中一起介绍。
(2)event_base_new_with_config()函数
首先看看刚刚提到的event_config结构体。Libevent是跨平台的Reactor,对于事件监听,其内部是使用多路IO复用函数。比较通用的多路IO复用函数是select和poll。而很多平台都提出了自己的高效多路IO复用函数,比如:epoll、devpoll、kqueue。Libevent对于这些多路IO复用函数都进行包装,供自己使用。event_config_avoid_method函数就是指出,避免使用指定的多路IO复用函数。其是通过字符串的方式指定的,即参数method。这个字符串将由队列元素event_config_entry的avoid_method成员变量存储(由于是指针,所以更准确来说是指向)。
查看Libevent源码包里的文件,可以发现有诸如epoll.c、select.c、poll.c、devpoll.c、kqueue.c这些文件。打开这些文件就可以发现在文件内容的前面都会定义一个struct eventop类型变量。该结构体的第一个成员必然是一个字符串。这个字符串就描述了对应的多路IO复用函数的名称。所以是可以通过名称来禁用某种多路IO复用函数的。
/*队列元素的类型*/
struct event_config_entry {
TAILQ_ENTRY(event_config_entry) next;
const char *avoid_method;
};
/** Internal structure: describes the configuration we want for an event_base
* that we're about to allocate. */
struct event_config {
/*生成队列管理配置*/
TAILQ_HEAD(event_configq, event_config_entry) entries;
/*CPU数量,其作用是告诉event_config,系统中有多少个CPU,以便作一些对线程池作一些调整来获取更高的效率*/
int n_cpus_hint;
/*限制参数:最大分发事件间隔时间,最大回调次数,优先级限制*/
struct timeval max_dispatch_interval;
int max_dispatch_callbacks;
int limit_callbacks_after_prio;
/*指定多路IO复用函数应该满足哪些特征*/
enum event_method_feature require_features;
/*通过函数event_config_set_flag设置*/
enum event_base_config_flag flags;
};
enum event_method_feature {
//支持边沿触发
EV_FEATURE_ET = 0x01,
//添加、删除、或者确定哪个事件激活这些动作的时间复杂度都为O(1)
//select、poll是不能满足这个特征的.epoll则满足
EV_FEATURE_O1 = 0x02,
//支持任意的文件描述符,而不能仅仅支持套接字
EV_FEATURE_FDS = 0x04
};
其中flags取:
EVENT_BASE_FLAG_NOLOCK:不要为event_base分配锁。设置这个选项可以为event_base节省一点加锁和解锁的时间,但是当多个线程访问event_base会变得不安全
EVENT_BASE_FLAG_IGNORE_ENV:选择多路IO复用函数时,不检测EVENT_*环境变量。使用这个标志要考虑清楚:因为这会使得用户更难调试程序与Libevent之间的交互
EVENT_BASE_FLAG_STARTUP_IOCP:仅用于Windows。这使得Libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用。如果设置了这个宏,那么evconn_listener_new和bufferevent_socket_new函数的内部将使用IOCP
EVENT_BASE_FLAG_NO_CACHE_TIME:在执行event_base_loop的时候没有cache时间。该函数的while循环会经常取系统时间,如果cache时间,那么就取cache的。如果没有的话,就只能通过系统提供的函数来获取系统时间。这将更耗时
EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST:告知Libevent,如果决定使用epoll这个多路IO复用函数,可以安全地使用更快的基于changelist 的多路IO复用函数:epoll-changelist多路IO复用可以在多路IO复用函数调用之间,同样的fd 多次修改其状态的情况下,避免不必要的系统调用。但是如果传递任何使用dup()或者其变体克隆的fd给Libevent,epoll-changelist多路IO复用函数会触发一个内核bug,导致不正确的结果。在不使用epoll这个多路IO复用函数的情况下,这个标志是没有效果的。也可以通过设置EVENT_EPOLL_USE_CHANGELIST 环境变量来打开epoll-changelist选项。
下面是event_base_new_with_config()函数:
struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
int i;
struct event_base *base;
int should_check_environment;
#ifndef EVENT__DISABLE_DEBUG_MODE
event_debug_mode_too_late = 1;
#endif
/* 之所以不用mm_malloc是因为mm_malloc并不会清零该内存区域。
* 而这个函数是会清零申请到的内存区域,这相当于被base初始化
*/
if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
event_warn("%s: calloc", __func__);
return NULL;
}
/*根据config的flags赋值*/
if (cfg)
base->flags = cfg->flags;
/*检查环境时间:可能系统时间发生了变化*/
should_check_environment =
!(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));
{
struct timeval tmp;
int precise_time =
cfg && (cfg->flags & EVENT_BASE_FLAG_PRECISE_TIMER);
int flags;
if (should_check_environment && !precise_time) {
precise_time = evutil_getenv_("EVENT_PRECISE_TIMER") != NULL;
if (precise_time) {
base->flags |= EVENT_BASE_FLAG_PRECISE_TIMER;
}
}
flags = precise_time ? EV_MONOT_PRECISE : 0;
evutil_configure_monotonic_time_(&base->monotonic_timer, flags);
gettime(base, &tmp);
}
/*初始化需要用到的堆、pair、fd、哈希列表等*/
min_heap_ctor_(&base->timeheap);
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;
TAILQ_INIT(&base->active_later_queue);
evmap_io_initmap_(&base->io);
evmap_signal_initmap_(&base->sigmap);
event_changelist_init_(&base->changelist);
base->evbase = NULL;
/*根据cfg赋值*/
if (cfg) {
memcpy(&base->max_dispatch_time,
&cfg->max_dispatch_interval, sizeof(struct timeval));
base->limit_callbacks_after_prio =
cfg->limit_callbacks_after_prio;
} else {
base->max_dispatch_time.tv_sec = -1;
base->limit_callbacks_after_prio = 1;
}
if (cfg && cfg->max_dispatch_callbacks >= 0) {
base->max_dispatch_callbacks = cfg->max_dispatch_callbacks;
} else {
base->max_dispatch_callbacks = INT_MAX;
}
if (base->max_dispatch_callbacks == INT_MAX &&
base->max_dispatch_time.tv_sec == -1)
base->limit_callbacks_after_prio = INT_MAX;
/*选择IO复用结构体*/
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;
base->evsel = eventops[i];
base->evbase = base->evsel->init(base);
}
/*检查event_base是否为空,若空则报错*/
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
* 测试evthread_lock_callbacks结构中的lock指针函数是否为NULL
* 即测试Libevent是否已经初始化为支持多线程模式。
* 由于一开始是用mm_calloc申请内存的,所以该内存区域的值为0
* 对于th_base_lock变量,目前的值为NULL.
*/
#if !defined(EVENT__DISABLE_THREAD_SUPPORT) && !defined(EVENT__DISABLE_DEBUG_MODE)
event_debug_created_threadable_ctx_ = 1;
#endif
#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, 0);
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
/*检测是否开启IOCP*/
#ifdef _WIN32
if (cfg && (cfg->flags & EVENT_BASE_FLAG_STARTUP_IOCP))
event_base_start_iocp_(base, cfg->n_cpus_hint);
#endif
return (base);
}
(3)event_base_loop()函数
Libevent的事件主循环主要是通过event_base_loop ()函数完成的,即根据系统提供的事件多路分发机制执行事件循环,对已注册的就绪事件,调用注册事件的回调函数来处理事件。
/*事件主循环部分*/
int
event_base_loop(struct event_base *base, int flags)
{
const struct eventop *evsel = base->evsel;
struct timeval tv;
struct timeval *tv_p;
int res, done, retval = 0;
/* Grab the lock. We will release it inside evsel.dispatch, and again
* as we invoke user callbacks. */
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
/*异常检测*/
if (base->running_loop) {
event_warnx("%s: reentrant invocation. Only one event_base_loop"
" can run on each event_base at once.", __func__);
EVBASE_RELEASE_LOCK(base, th_base_lock);
return -1;
}
base->running_loop = 1;
/*清空时间缓存*/
clear_time_cache(base);
if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
evsig_set_base_(base);
done = 0;
#ifndef EVENT__DISABLE_THREAD_SUPPORT
base->th_owner_id = EVTHREAD_GET_ID();
#endif
base->event_gotterm = base->event_break = 0;
/*事件主循环*/
while (!done) {
base->event_continue = 0;
base->n_deferreds_queued = 0;
/* 查看是否需要跳出循环,程序可以调用event_loopexit_cb()设置event_gotterm标记
* 调用event_base_loopbreak()设置event_break标记
* Terminate the loop if we have been asked to
*/
if (base->event_gotterm) {
break;
}
if (base->event_break) {
break;
}
/*根据timer heap中事件的最小超时时间,计算系统I/O demultiplexer的最大等待时间 */
tv_p = &tv;
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
timeout_next(base, &tv_p);
} else {
/* 依然有未处理的就绪时间,就让I/O demultiplexer立即返回,不必等待
* 下面会提到,在libevent中,低优先级的就绪事件可能不能立即被处理
* if we have active events, we just poll new events
* without waiting.
*/
evutil_timerclear(&tv);
}
/* 如果当前没有注册事件,就退出 If we have no events, we just exit */
if (0==(flags&EVLOOP_NO_EXIT_ON_EMPTY) &&
!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
event_debug(("%s: no events registered.", __func__));
retval = 1;
goto done;
}
event_queue_make_later_events_active(base);
clear_time_cache(base);
/* 调用系统I/O demultiplexer等待就绪I/O events,可能是epoll_wait,或者select等;
// 在evsel->dispatch()中,会把就绪signal event、I/O event插入到激活链表中 */
res = evsel->dispatch(base, tv_p);
if (res == -1) {
event_debug(("%s: dispatch returned unsuccessfully.",
__func__));
retval = -1;
goto done;
}
update_time_cache(base);
timeout_process(base);
/* 调用event_process_active()处理激活链表中的就绪event,调用其回调函数执行事件处理
* 该函数会寻找最高优先级(priority值越小优先级越高)的激活事件链表,
* 然后处理链表中的所有就绪事件;
* 因此低优先级的就绪事件可能得不到及时处理;
*/
if (N_ACTIVE_CALLBACKS(base)) {
int n = event_process_active(base);
if ((flags & EVLOOP_ONCE)
&& N_ACTIVE_CALLBACKS(base) == 0
&& n != 0)
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
event_debug(("%s: asked to terminate loop.", __func__));
/*循环结束,清空时间缓存 */
done:
clear_time_cache(base);
base->running_loop = 0;
EVBASE_RELEASE_LOCK(base, th_base_lock);
return (retval);
}
###三.小结
本文分析了event_base的接口函数,通过了解这些接口函数,我们学习了event_base的功能、libevent的事件循环过程等。