Nginx事件驱动是根据操作系统以及操作系统内核设计了很多不同的驱动方式,epoll事件驱动机制是Unix系统的事件驱动模式。本文重点介绍ngx_epoll_module是如何基于Linux内核实现epoll事件驱动模型,实理解Nginx在几十万并发连接下高效利用服务器资源的基本原理。
epoll在处理用户请求时不同于select或者poll的模式(采用轮询来处理的,轮询的fd数目越多,耗时越多)将用户请求作为一个整体处理,epoll在Linux系统中申请一个简易的文件系统,将用户请求分为三个阶段:第一个阶段调用epoll_create建立一个epoll对象(分配文件系统句柄资源),然后调用epoll_ctl向epoll对象中添加连接套接字,最后调用epoll_wait收集发生事件的连接。实现了在进程启动时一次创建,重复使用的目标。
1.epoll_create创建文件系统句柄
创建epoll句柄,实质就是从内核申请一个内存空间,用来存放socket事件(具体参考Linux句柄的使用)。当epoll句柄创建完成后,就会占用一个fd值,在使用完后需要调用close方法释放fd句柄,否则句柄一直处于被占用状态。具体使用参考ngx_epoll_init方法
int epoll_create(int size);
static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
ngx_epoll_conf_t *epcf;
epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
if (ep == -1) {
ep = epoll_create(cycle->connection_n / 2);
if (ep == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"epoll_create() failed");
return NGX_ERROR;
}
#if (NGX_HAVE_EVENTFD)
if (ngx_epoll_notify_init(cycle->log) != NGX_OK) {
ngx_epoll_module_ctx.actions.notify = NULL;
}
#endif
#if (NGX_HAVE_FILE_AIO)
ngx_epoll_aio_init(cycle, epcf);
#endif
#if (NGX_HAVE_EPOLLRDHUP)
ngx_epoll_test_rdhup(cycle);
#endif
}
if (nevents < epcf->events) {
if (event_list) {
ngx_free(event_list);
}
event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
cycle->log);
if (event_list == NULL) {
return NGX_ERROR;
}
}
nevents = epcf->events;
ngx_io = ngx_os_io;
ngx_event_actions = ngx_epoll_module_ctx.actions;
#if (NGX_HAVE_CLEAR_EVENT)
ngx_event_flags = NGX_USE_CLEAR_EVENT
#else
ngx_event_flags = NGX_USE_LEVEL_EVENT
#endif
|NGX_USE_GREEDY_EVENT
|NGX_USE_EPOLL_EVENT;
return NGX_OK;
}
2.epoll_ctl对监听事件管理
主要是将被监听的事件添加到epoll句柄(epoll_create中创建的fd句柄,)、删除或者修改。
/*
*epfd :epoll_create中创建的句柄EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
*op:操作类型,例如:事件注册(EPOLL_CTL_ADD), 修改(EPOLL_CTL_MOD),删除(EPOLL_CTL_DEL)。
*fd:关联的文件描述符
*event:epoll_event指针(文件可读EPOLLIN,文件可写EPOLLOUT,紧急数据EPOLLPRI,文件错误EPOLLERR,文件挂断
EPOLLHUP)
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
#if (NGX_HAVE_EVENTFD)
static ngx_int_t
ngx_epoll_notify_init(ngx_log_t *log)
{
struct epoll_event ee;
#if (NGX_HAVE_SYS_EVENTFD_H)
notify_fd = eventfd(0, 0);
#else
notify_fd = syscall(SYS_eventfd, 0);
#endif
if (notify_fd == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "eventfd() failed");
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
"notify eventfd: %d", notify_fd);
notify_event.handler = ngx_epoll_notify_handler;
notify_event.log = log;
notify_event.active = 1;
notify_conn.fd = notify_fd;
notify_conn.read = ¬ify_event;
notify_conn.log = log;
ee.events = EPOLLIN|EPOLLET;
ee.data.ptr = ¬ify_conn;
if (epoll_ctl(ep, EPOLL_CTL_ADD, notify_fd, &ee) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"epoll_ctl(EPOLL_CTL_ADD, eventfd) failed");
if (close(notify_fd) == -1) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
"eventfd close() failed");
}
return NGX_ERROR;
}
return NGX_OK;
}
3.epoll_wait等待事件触发
轮询epfd上的io事件(epoll_ctl注册的事件都是文件事件),如果事件发生则将socket句柄和事件类型放入event数组中,如果等待事件超过time_out,则超时处理。
int epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout);
事件轮询模式中最重要的功能就是事件的收集,分发。ngx_epoll_process_events实现了process_events接口,通过轮询epoll_wait返回的事件数组,将其分发给对于的handler处理,具体参考代码。
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
int events;
uint32_t revents;
ngx_int_t instance, i;
ngx_uint_t level;
ngx_err_t err;
ngx_event_t *rev, *wev;
ngx_queue_t *queue;
ngx_connection_t *c;
/* NGX_TIMER_INFINITE == INFTIM */
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll timer: %M", timer);
events = epoll_wait(ep, event_list, (int) nevents, timer);
err = (events == -1) ? ngx_errno : 0;
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
ngx_time_update();
}
if (err) {
if (err == NGX_EINTR) {
if (ngx_event_timer_alarm) {
ngx_event_timer_alarm = 0;
return NGX_OK;
}
level = NGX_LOG_INFO;
} else {
level = NGX_LOG_ALERT;
}
ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
return NGX_ERROR;
}
if (events == 0) {
if (timer != NGX_TIMER_INFINITE) {
return NGX_OK;
}
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"epoll_wait() returned no events without timeout");
return NGX_ERROR;
}
for (i = 0; i < events; i++) {
c = event_list[i].data.ptr;
instance = (uintptr_t) c & 1;
c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
rev = c->read;
if (c->fd == -1 || rev->instance != instance) {
/*
* the stale event from a file descriptor
* that was just closed in this iteration
*/
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll: stale event %p", c);
continue;
}
revents = event_list[i].events;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll: fd:%d ev:%04XD d:%p",
c->fd, revents, event_list[i].data.ptr);
if (revents & (EPOLLERR|EPOLLHUP)) {
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll_wait() error on fd:%d ev:%04XD",
c->fd, revents);
/*
* if the error events were returned, add EPOLLIN and EPOLLOUT
* to handle the events at least in one active handler
*/
revents |= EPOLLIN|EPOLLOUT;
}
#if 0
if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"strange epoll_wait() events fd:%d ev:%04XD",
c->fd, revents);
}
#endif
if ((revents & EPOLLIN) && rev->active) {
#if (NGX_HAVE_EPOLLRDHUP)
if (revents & EPOLLRDHUP) {
rev->pending_eof = 1;
}
#endif
rev->ready = 1;
rev->available = -1;
if (flags & NGX_POST_EVENTS) {
queue = rev->accept ? &ngx_posted_accept_events
: &ngx_posted_events;
ngx_post_event(rev, queue);
} else {
rev->handler(rev);
}
}
wev = c->write;
if ((revents & EPOLLOUT) && wev->active) {
if (c->fd == -1 || wev->instance != instance) {
/*
* the stale event from a file descriptor
* that was just closed in this iteration
*/
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll: stale event %p", c);
continue;
}
wev->ready = 1;
#if (NGX_THREADS)
wev->complete = 1;
#endif
if (flags & NGX_POST_EVENTS) {
ngx_post_event(wev, &ngx_posted_events);
} else {
wev->handler(wev);
}
}
}
return NGX_OK;
}