Lighttpd1.4.20原始碼分析 筆記 fdevent系統-初始化
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的初始化工作全部完成。