C程序在进行真正的编译之前都要进行预编译。
我们看看fdevent系统中的一些宏:
~~~
#if defined(HAVE_EPOLL_CTL) && defined(HAVE_SYS_EPOLL_H)
# if defined HAVE_STDINT_H
# include <stdint.h>
# endif
# define USE_LINUX_EPOLL
# include <sys/epoll.h>
#endif
#if defined HAVE_POLL && (defined(HAVE_SYS_POLL_H) || defined(HAVE_POLL_H))
# define USE_POLL
# ifdef HAVE_POLL_H
# include <poll.h>
# else
# include <sys/poll.h>
# endif
# if defined HAVE_SIGTIMEDWAIT && defined(__linux__)
# define USE_LINUX_SIGIO
# include <signal.h>
# endif
#endif
//……
~~~
上面的宏判断系统中是否有对应的多路IO系统,如果有,就定义对应的USE_XXX宏。
预编译完这些宏以后,对于当前系统中有的多路IO系统,就会有对应的USE_XXX符号被定义。预编译器接着运行,将那些不需要的代码都忽略。
fdevent.h中对所有可能的多路IO系统都定义了初始化函数:
~~~
int fdevent_select_init(fdevents * ev);
int fdevent_poll_init(fdevents * ev);
int fdevent_linux_rtsig_init(fdevents * ev);
int fdevent_linux_sysepoll_init(fdevents * ev);
int fdevent_solaris_devpoll_init(fdevents * ev);
int fdevent_freebsd_kqueue_init(fdevents * ev);
~~~
因此,对于系统中没有的多路IO系统对应的初始化函数,预编译结束后,这些初始化函数被定义为报错函数。如epoll对应的为:
~~~
#ifdef USE_LINUX_EPOLL
/* 当定义了epoll时,epoll的函数实现代码 */
#else
/* 当未定义epoll时,epoll只实现init函数(这是必须的,因为该函数在fdevent.h中定义了),并将它实现为报错函数 */
int fdevent_linux_sysepoll_init(fdevents *ev) {
UNUSED(ev);
fprintf(stderr, "%s.%d: linux-sysepoll not supported, try to set server.event-handler = \"poll\" or \"select\"\n",
__FILE__, __LINE__);
return -1;
}
#endif
~~~
预编译后,开始真正的编译。我们假设系统中只有epoll。
首先,我们看一看配置中有关fdevent的设置。进入configfile.c文件中的config_set_defaults()函数。函数的一开始就有这么一个定义:
~~~
struct ev_map
{
fdevent_handler_t et;
const char *name;
} event_handlers[] =
{
/*
* - poll is most reliable - select works everywhere -
* linux-* are experimental
*/
#ifdef USE_POLL
{FDEVENT_HANDLER_POLL, "poll"},
#endif
#ifdef USE_SELECT
{FDEVENT_HANDLER_SELECT, "select"},
#endif
#ifdef USE_LINUX_EPOLL
{FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll"},
#endif
#ifdef USE_LINUX_SIGIO
{FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig"},
#endif
#ifdef USE_SOLARIS_DEVPOLL
{FDEVENT_HANDLER_SOLARIS_DEVPOLL, "solaris-devpoll"},
#endif
#ifdef USE_FREEBSD_KQUEUE
{FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue"},
{FDEVENT_HANDLER_FREEBSD_KQUEUE, "kqueue"},
#endif
{FDEVENT_HANDLER_UNSET, NULL}
};
~~~
上面定义了一个struct ev_map类型的数组。数组的内容是当前系统中存在的多路IO系统的类型和名称。这里排序很有意思,从注释中可以看出,poll排在最前因为最可靠,select其次因为支持最广泛,epoll第三因为是最好的。
在这里,如果配置文件有配置,那么按配置文件来,如果没配置,则取上面数组中的第一个。以下是配置文件的格式:
~~~
## set the event-handler (read the performance
##section in the manual)
server.event-handler = "freebsd-kqueue" # needed on OS X
~~~
接下来我们看server.c中的main函数。
前面有一些是设置fd数量的,其中select比较特殊需要特别处理,fd数量一个是系统的限制,一个是用户配置的限制。
当程序产生子进程后,在子进程中执行的第一条语句就是初始化fdevent系统:
~~~
if (NULL == (srv->ev = fdevent_init(srv->max_fds + 1, srv->event_handler))) {
log_error_write(srv, __FILE__, __LINE__,
"s", "fdevent_init failed");
return -1;
}
~~~
进入fdevent_init()函数:
~~~
/**
* 初始化文件描述符事件数组fdevent
*/
fdevents *fdevent_init(size_t maxfds, fdevent_handler_t type)
{
fdevents *ev;
//内存被初始化为0
ev = calloc(1, sizeof(*ev));
//分配数组
ev->fdarray = calloc(maxfds, sizeof(*ev->fdarray));
ev->maxfds = maxfds;
//根据设定的多路IO的类型进行初始化。
switch (type)
{
case FDEVENT_HANDLER_POLL:
if (0 != fdevent_poll_init(ev))
{
fprintf(stderr, "%s.%d: event-handler poll failed\n", __FILE__, __LINE__);
return NULL;
}
break;
case FDEVENT_HANDLER_SELECT:
if (0 != fdevent_select_init(ev))
{
fprintf(stderr, "%s.%d: event-handler select failed\n", __FILE__, __LINE__);
return NULL;
}
break;
case FDEVENT_HANDLER_LINUX_RTSIG:
if (0 != fdevent_linux_rtsig_init(ev))
{
fprintf(stderr, "%s.%d: event-handler linux-rtsig failed, try to set server.event-handler = \"poll\" or \"select\"\n",
__FILE__, __LINE__);
return NULL;
}
break;
case FDEVENT_HANDLER_LINUX_SYSEPOLL:
if (0 != fdevent_linux_sysepoll_init(ev))
{
fprintf(stderr, "%s.%d: event-handler linux-sysepoll failed, try to set server.event-handler = \"poll\" or \"select\"\n",
__FILE__, __LINE__);
return NULL;
}
break;
case FDEVENT_HANDLER_SOLARIS_DEVPOLL:
if (0 != fdevent_solaris_devpoll_init(ev))
{
fprintf(stderr, "%s.%d: event-handler solaris-devpoll failed, try to set server.event-handler = \"poll\" or \"select\"\n",
__FILE__, __LINE__);
return NULL;
}
break;
case FDEVENT_HANDLER_FREEBSD_KQUEUE:
if (0 != fdevent_freebsd_kqueue_init(ev))
{
fprintf(stderr, "%s.%d: event-handler freebsd-kqueue failed, try to set server.event-handler = \"poll\" or \"select\"\n",
__FILE__, __LINE__);
return NULL;
}
break;
default:
fprintf(stderr, "%s.%d: event-handler is unknown, try to set server.event-handler = \"poll\" or \"select\"\n",
__FILE__, __LINE__);
return NULL;
}
return ev;
}
~~~
fdevent_init()函数根据fdevent_handler_t的值调用相应的初始化函数。我们进入fdevent_linux_sysepoll_init()函数:
~~~
int fdevent_linux_sysepoll_init(fdevents * ev)
{
ev->type = FDEVENT_HANDLER_LINUX_SYSEPOLL;
#define SET(x) \
ev->x = fdevent_linux_sysepoll_##x;
/* 通过SET宏对fdevents结构体中的函数指针赋值。然后创建epoll,最后做一些设置。*/
SET(free);
SET(poll);
SET(event_del);
SET(event_add);
SET(event_next_fdndx);
SET(event_get_fd);
SET(event_get_revent);
//创建epoll
if (-1 == (ev->epoll_fd = epoll_create(ev->maxfds)))
{
fprintf(stderr, "%s.%d: epoll_create failed (%s), try
to set server.event-handler = \"poll\" or \"select\"\n",
__FILE__, __LINE__, strerror(errno));
return -1;
}
//设置epoll_fd为运行exec()函数时关闭。
if (-1 == fcntl(ev->epoll_fd, F_SETFD, FD_CLOEXEC))
{
fprintf(stderr, "%s.%d: epoll_create failed (%s), try
to set server.event-handler = \"poll\" or \"select\"\n",
__FILE__, __LINE__, strerror(errno));
close(ev->epoll_fd);
return -1;
}
//创建fd事件数组。在epoll_wait函数中使用。
//存储发生了IO事件的fd和对应的IO事件。
ev->epoll_events = malloc(ev->maxfds *
sizeof(*ev->epoll_events));
return 0;
}
~~~
至此fdevent的初始化工作全部完成。