1. libevent 到底使用哪种 io 模式来作为底层实现
libevent 实际封装了很多 IO 复用模式,比如 evport,select,poll,epoll,devpoll 等等,这些都是不同操作系统下的 I / O 多路复用模式,那么我们怎么知道当前使用的是哪种模式呢?
说到底层实现,那就不得不说说 event-config.h 文件的生成。
1.1 event-config.h 文件的生成
在上一篇《libevent 目录结构分析》中,我们提到 event-config.h,它存放了很多宏定义配置。
event-config.h 这个文件并不是一直不变的,这里有一个过程:
- 首先,configure 在检查环境依赖的时候会生成 config.h 和 Makefile;
- 然后,Makefile 会根据 config.h 生成 event-config.h;
event-config.h 基本是存放宏定义的,如下:
/* Define to 1 if you have the <dlfcn.h> header file. */
#define EVENT__HAVE_DLFCN_H 1
/* Define if your system supports the epoll system calls */
#define EVENT__HAVE_EPOLL 1
/* Define to 1 if you have the `epoll_create1' function. */
/* #undef EVENT__HAVE_EPOLL_CREATE1 */
/* Define to 1 if you have the `epoll_ctl' function. */
#define EVENT__HAVE_EPOLL_CTL 1
/* Define to 1 if you have the <errno.h> header file. */
#define EVENT__HAVE_ERRNO_H 1
这个是在我的 redhat 环境下编译以后生成的部分宏定义,EVENT__HAVE_EPOLL 这个宏定义为 1 则说明我的 redhat 环境是支持 epoll 的,但这并不能说明我当前就是使用的 epoll,因为 redhat 同样支持 select 和 poll。
1.2 I/ O 模式选择
那么底层实现到底用哪个呢,看下面代码:
struct event_config *config;
struct event_base *base;
/* Create a new configuration object. */
config = event_config_new();
/* We don't want to use the"select" method. */
event_config_avoid_method(config, "select");
/* We want a method that can work with non-socket file descriptors */
event_config_require_features(config, EV_FEATURE_FDS);
base = event_base_new_with_config(config);
if (!base) { /* There is no backend method that does what we want. */
exit(1); }
event_config_free(config);
这是源码包里面 whatsnew-2.0.txt 里面的例子,event_config_require_features 这个函数其实就指定了底层实现,那么具体过程是怎样的呢。
- 首先,event_config_new 生成了一个 event_config,struct event_config 定义如下:
struct event_config {TAILQ_HEAD(event_configq, event_config_entry) entries;
int n_cpus_hint;
struct timeval max_dispatch_interval;
int max_dispatch_callbacks;
int limit_callbacks_after_prio;
enum event_method_feature require_features;
enum event_base_config_flag flags;
};
里面的枚举 require_features 就是决定最终用哪个,而 event_config_require_features 指定了 require_features 的值。
- 然后在 event_base_new_with_config 函数中,有如下代码:
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;
/*
这里不符合我们 cfg->require_features 指定的 I / O 都不会往下走,只有满足条件的才写到 event_base 里面去
*/
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);
}
这段代码中,cfg 就是上面的 config,EV_FEATURE_FDS 是 0x04, 而 eventops 定义如下:
/* Array of backends in order of preference. */
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
};
eventops 是一个结构体指针数组,它里面具体有哪些选项就是根据 event-config.h 里面宏定义来的,里面元素 evportops 等这些都是定义在各个 I / O 模式的封装文件里,例如 epollops 就是在 epoll.c 里面的, selectops 则是在 select.c 中定义的。
查看 eventops 各个元素里面 features 字段的值,发现 devpollops,pollops,selectops 这三种与 0x04 是能对上的,而 devpoll 并不是 redhat 的 IO, 那么在 redhat 环境中符合条件的就是 poll 和 select,而 event_config_avoid_method(config, “select”) 这个函数调用指定不使用 select,所以最终使用的就是 poll。
那如果没有显示指定底层实现呢,比如这样的:
struct event_base *base = event_base_new();
还是里面的这段代码:
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;
/*
这里不符合我们 cfg->require_features 指定的 I / O 都不会往下走,只有满足条件的才写到 event_base 里面去
*/
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);
}
符合条件的是 epoll,poll,select, 根据 for 循环条件!base->evbase 可知,第一个 epoll 赋给 base->evbase 后,循环就会结束,所以默认就是 epoll。
cpp 加油站 (ID:xy13640954449), 作者 linux 服务器开发老司机,欢迎来撩!