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的初始化工作全部完成。