在linux后台服务器开发领域里面,epoll的大名是早有所闻。《深入理解nginx》一书在第9章-事件模块中就详细说明了epoll相关的系统调用是怎么嵌入到nginx的框架中。
下面说明nginx框架下与事件处理相关的一些模块。
一.ngx_events_module
ngx_events_module是核心模块中的一种。之前一直不是很明白核心模块的意思,现在想来,事件模块的核心模块应该是第一个启动的与事件相关的模块。这个模块并不会去处理实际的事件业务,而是会去做一些基本的初始化操作。ngx_events_module的定义如下:
ngx_module_t ngx_events_module = {
NGX_MODULE_V1,
&ngx_events_module_ctx, /* module context */
ngx_events_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_command_t ngx_events_commands[] = {
{ ngx_string("events"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_events_block,
0,
0,
NULL },
ngx_null_command
};
static ngx_core_module_t ngx_events_module_ctx = {
ngx_string("events"),
NULL,
NULL
};
从上述程序可以看到,本模块做的事情很有限,在ngx_events_module_ctx中的init_conf和create_conf方法都是空的。而ngx_events_block中则会去调用其他的事件模块去解析events中的配置,并且调用其他事件模块各自的方法去构建并填充配置项结构体。部分核心代码如下:
//conf为ngx_conf_handler中的conf = confp[ngx_modules[i]->ctx_index];也就是conf指向的是ngx_cycle_s->conf_ctx[],
//所以对conf赋值就是对ngx_cycle_s中的conf_ctx赋值,最终就是ngx_cycle_s中的conf_ctx[ngx_events_module=>index]指向了ctx
*(void **) conf = ctx;
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
m = ngx_modules[i]->ctx;
if (m->create_conf) {
(*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle);
if ((*ctx)[ngx_modules[i]->ctx_index] == NULL) {
return NGX_CONF_ERROR;
}
}
}
//零时保存之前的cf,在下面解析完event{}配置后,在恢复
pcf = *cf;
cf->ctx = ctx;
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;
rv = ngx_conf_parse(cf, NULL);//这时候cf里面的上下文ctx为NGX_EVENT_MODULE模块create_conf的用于存储event{}的空间
*cf = pcf; //解析完event{}配置后,恢复
if (rv != NGX_CONF_OK) {
return rv;
}
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
m = ngx_modules[i]->ctx;
if (m->init_conf) {
rv = m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]);
if (rv != NGX_CONF_OK) {
return rv;
}
}
}
二.ngx_event_core_module事件模块
ngx_event_core_module在事件模块中是第一位的顺序(上面的是核心模块,原则上讲不属于事件模块)。本模块会负责对events中的很多配置项做出响应,完成相应初始化操作,为后面实际的事件处理模块做准备。本模块的定义如下:
//相关配置见ngx_event_core_commands ngx_http_core_commands ngx_stream_commands ngx_http_core_commands ngx_core_commands ngx_mail_commands
static ngx_command_t ngx_event_core_commands[] = {
//每个worker进程可以同时处理的最大连接数
//连接池的大小,也就是每个worker进程中支持的TCP最大连接数,它与下面的connections配置项的意义是重复的,可参照9.3.3节理解连接池的概念
{ ngx_string("worker_connections"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_connections,
0,
0,
NULL },
//设置事件模型。 use [kqueue | rtsig | epoll | dev/poll | select | poll | eventport] linux系统中只支持select poll epoll三种
//freebsd里的kqueue,LINUX中没有
//确定选择哪一个事件模块作为事件驱动机制
{ ngx_string("use"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_use,
0,
0,
NULL },
//当事件模块通知有TCP连接时,尽可能在本次调度中对所有的客户端TCP连接请求都建立连接
//对应于事件定义的available字段。对于epoll事件驱动模式来说,意味着在接收到一个新连接事件时,调用accept以尽可能多地接收连接
{ ngx_string("multi_accept"),
NGX_EVENT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_event_conf_t, multi_accept),
NULL },
//accept_mutex on|off是否打开accept进程锁,是为了实现worker进程接收连接的负载均衡、打开后让多个worker进程轮流的序列号的接收TCP连接
//默认是打开的,如果关闭的话TCP连接会更快,但worker间的连接不会那么均匀。
{ ngx_string("accept_mutex"),
NGX_EVENT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_event_conf_t, accept_mutex),
NULL },
//accept_mutex_delay time,如果设置为accpt_mutex on,则worker同一时刻只有一个进程能个获取accept锁,这个accept锁不是阻塞的,如果娶不到会
//立即返回,然后等待time时间重新获取。
//启用accept_mutex负载均衡锁后,延迟accept_mutex_delay毫秒后再试图处理新连接事件
{ ngx_string("accept_mutex_delay"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
0,
offsetof(ngx_event_conf_t, accept_mutex_delay),
NULL },
//debug_connection 1.2.2.2则在收到该IP地址请求的时候,使用debug级别打印。其他的还是沿用error_log中的设置
//需要对来自指定IP的TCP连接打印debug级别的调斌日志
{ ngx_string("debug_connection"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_debug_connection,
0,
0,
NULL },
ngx_null_command
};
//ngx_event_core_module模块则仅实现了create_conf方法和init_conf方法,这是因为它并不真正负责TCP网络事件的驱动,
//所以不会实现ngx_event_actions_t中的方法
ngx_event_module_t ngx_event_core_module_ctx = {
&event_core_name,
ngx_event_core_create_conf, /* create configuration */
ngx_event_core_init_conf, /* init configuration */
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};
/*
Nginx定义了一系列(目前为9个)运行在不同操作系统、不同内核版本上的事件驱动模块,包括:ngx_epoll_module、ngx_kqueue_module、
ngx_poll_module、ngx_select_module、ngx_devpoll_module、ngx_eventport_module、ngx_aio_module、ngx_rtsig_module
和基于Windows的ngx_select_module模块。在ngx_event_core_module模块的初始化过程中,将会从以上9个模块中选取1个作为Nginx进程的事件驱动模块。
*/
ngx_module_t ngx_event_core_module = {
NGX_MODULE_V1,
&ngx_event_core_module_ctx, /* module context */
ngx_event_core_commands, /* module directives */
NGX_EVENT_MODULE, /* module type */
NULL, /* init master */
ngx_event_module_init, /* init module */ //解析完配置文件后执行
ngx_event_process_init, /* init process */ //在创建子进程的里面执行 ngx_worker_process_init
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
注意这里的ngx_event_process_init函数实际上是做了很多的事情,这个函数是会在创建子进程的时候被调用。具体参见书本说明。
三.ngx_epoll_module模块
当events里面的use 配置项选择的是epoll的时候,就会在后面对应到ngx_epoll_module模块的执行。本模块的定义如下:
static ngx_command_t ngx_epoll_commands[] = {
/*
在调用epoll_wait时,将由第2和第3个参数告诉Linux内核一次最多可返回多少个事件。这个配置项表示调用一次epoll_wait时最多可返回
的事件数,当然,它也会预分配那么多epoll_event结构体用于存储事件
*/
{ ngx_string("epoll_events"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,
offsetof(ngx_epoll_conf_t, events),
NULL },
/*
在开启异步I/O且使用io_setup系统调用初始化异步I/O上下文环境时,初始分配的异步I/O事件个数
*/
{ ngx_string("worker_aio_requests"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,
offsetof(ngx_epoll_conf_t, aio_requests),
NULL },
ngx_null_command
};
ngx_event_module_t ngx_epoll_module_ctx = {
&epoll_name,
ngx_epoll_create_conf, /* create configuration */
ngx_epoll_init_conf, /* init configuration */
{
ngx_epoll_add_event, /* add an event */ //ngx_add_event
ngx_epoll_del_event, /* delete an event */
ngx_epoll_add_event, /* enable an event */ //ngx_add_conn
ngx_epoll_del_event, /* disable an event */
ngx_epoll_add_connection, /* add an connection */
ngx_epoll_del_connection, /* delete an connection */
#if (NGX_HAVE_EVENTFD)
ngx_epoll_notify, /* trigger a notify */
#else
NULL, /* trigger a notify */
#endif
ngx_epoll_process_events, /* process the events */
ngx_epoll_init, /* init the events */
ngx_epoll_done, /* done the events */
}
};
ngx_module_t ngx_epoll_module = {
NGX_MODULE_V1,
&ngx_epoll_module_ctx, /* module context */
ngx_epoll_commands, /* module directives */
NGX_EVENT_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
ngx_epoll_init, ngx_epoll_addevent, ngx_epoll-process_events 中则是对应epoll_create,epoll_ctl, epoll_wait这些epoll的系统调用函数。
下面再来看看ngx_epoll_process_events的实现程序:
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)//flags参数中含有NGX_POST_EVENTS表示这批事件要延后处理
{
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;
char epollbuf[256];
/* NGX_TIMER_INFINITE == INFTIM */
//ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "begin to epoll_wait, epoll timer: %M ", timer);
/*
调用epoll_wait获取事件。注意,timer参数是在process_events调用时传入的,在9.7和9.8节中会提到这个参数
*/
//The call was interrupted by a signal handler before any of the requested events occurred or the timeout expired;
//如果有信号发生(见函数ngx_timer_signal_handler),如定时器,则会返回-1
//需要和ngx_add_event与ngx_add_conn配合使用
//event_list存储的是就绪好的事件,如果是select则是传入用户注册的事件,需要遍历检查,而且每次select返回后需要重新设置事件集,epoll不用
/*
这里面等待的事件包括客户端连接事件(这个是从父进程继承过来的ep,然后在子进程while前的ngx_event_process_init->ngx_add_event添加),
对已经建立连接的fd读写事件的添加在ngx_event_accept->ngx_http_init_connection->ngx_handle_read_event
*/
/*
ngx_notify->ngx_epoll_notify只会触发epoll_in,不会同时引发epoll_out,如果是网络读事件epoll_in,则会同时引起epoll_out
*/
events = epoll_wait(ep, event_list, (int) nevents, timer); //timer为-1表示无限等待 nevents表示最多监听多少个事件,必须大于0
//EPOLL_WAIT如果没有读写事件或者定时器超时事件发生,则会进入睡眠,这个过程会让出CPU
err = (events == -1) ? ngx_errno : 0;
//当flags标志位指示要更新时间时,就是在这里更新的
//要摸ngx_timer_resolution毫秒超时后跟新时间,要摸epoll读写事件超时后跟新时间
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
ngx_time_update();
}
if (err) {
if (err == NGX_EINTR) {
if (ngx_event_timer_alarm) { //定时器超时引起的epoll_wait返回
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;
}
//遍历本次epoll_wait返回的所有事件
for (i = 0; i < events; i++) { //和ngx_epoll_add_event配合使用
/*
c = event_list[i].data.ptr; //通过这个确定是那个连接
instance = (uintptr_t) c & 1; //将地址的最后一位取出来,用instance变量标识, 见ngx_epoll_add_event
/*
无论是32位还是64位机器,其地址的最后1位肯定是0,可以用下面这行语句把ngx_connection_t的地址还原到真正的地址值
*/ //注意这里的c有可能是accept前的c,用于检测是否客户端发起tcp连接事件,accept返回成功后会重新创建一个ngx_connection_t,用来读写客户端的数据
c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
rev = c->read; //取出读事件 //注意这里的c有可能是accept前的c,用于检测是否客户端发起tcp连接事件,accept返回成功后会重新创建一个ngx_connection_t,用来读写客户端的数据
if (c->fd == -1 || rev->instance != instance) { //判断这个读事件是否为过期事件
//当fd套接字描述符为-l或者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_epoll_event_2str(revents, epollbuf);
memset(epollbuf, 0, sizeof(epollbuf));
ngx_epoll_event_2str(revents, epollbuf);
ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll: fd:%d %s(ev:%04XD) d:%p",
c->fd, epollbuf, revents, event_list[i].data.ptr);
if (revents & (EPOLLERR|EPOLLHUP)) { //例如对方close掉套接字,这里会感应到
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll_wait() error on fd:%d ev:%04XD",
c->fd, revents);
}
#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 & (EPOLLERR|EPOLLHUP))
&& (revents & (EPOLLIN|EPOLLOUT)) == 0)
{
/*
* if the error events were returned without EPOLLIN or EPOLLOUT,
* then add these flags to handle the events at least in one
* active handler
*/
revents |= EPOLLIN|EPOLLOUT; //epoll EPOLLERR|EPOLLHUP实际上是通过触发读写事件进行读写操作recv write来检测连接异常
}
if ((revents & EPOLLIN) && rev->active) { //如果是读事件且该事件是活跃的
#if (NGX_HAVE_EPOLLRDHUP)
if (revents & EPOLLRDHUP) {
rev->pending_eof = 1;
}
#endif
//注意这里的c有可能是accept前的c,用于检测是否客户端发起tcp连接事件,accept返回成功后会重新创建一个ngx_connection_t,用来读写客户端的数据
rev->ready = 1; //表示已经有数据到了这里只是把accept成功前的 ngx_connection_t->read->ready置1,accept返回后会重新从连接池中获取一个ngx_connection_t
//flags参数中含有NGX_POST_EVENTS表示这批事件要延后处理
if (flags & NGX_POST_EVENTS) {
/*
如果要在post队列中延后处理该事件,首先要判断它是新连接事件还是普通事件,以决定把它加入到ngx_posted_accept_events队
列或者ngx_postedL events队列中。关于post队列中的事件何时执行
*/
queue = rev->accept ? &ngx_posted_accept_events
: &ngx_posted_events;
ngx_post_event(rev, queue);
} else {
//如果接收到客户端数据,这里为ngx_http_wait_request_handler
rev->handler(rev); //如果为还没accept,则为ngx_event_process_init中设置为ngx_event_accept。如果已经建立连接,则读数据为ngx_http_process_request_line
}
}
wev = c->write;
if ((revents & EPOLLOUT) && wev->active) {
if (c->fd == -1 || wev->instance != instance) { //判断这个读事件是否为过期事件
//当fd套接字描述符为-1或者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 (flags & NGX_POST_EVENTS) {
ngx_post_event(wev, &ngx_posted_events); //将这个事件添加到post队列中延后处理
} else { //立即调用这个写事件的回调方法来处理这个事件
wev->handler(wev);
}
}
}
return NGX_OK;
}
可知本模块只负责事件的分发,具体的业务逻辑处理是在各个读写事件对应的handler方法中。