在前面的文章我们已经知道,worker进程会调用所有模块的init process 函数,在ngx_event_core_module模块中,如果使用的是epoll模块的话,那么该函数会将epoll模块定义的方法关联起来,那么以后就可以直接使用epoll模块了。
然后究竟是如何与epoll模块关联起来的呢?嗯,先看ngx_event_core_module模块的ngx_event_process_init函数的如下代码:
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
continue; //遍历全局的ngx_modules数组,如果不是事件模块,那么直接跳过
}
if (ngx_modules[m]->ctx_index != ecf->use) { //如果当前这个事件模块不是要用的,那么也跳过,貌似默认的是epoll
continue;
}
module = ngx_modules[m]->ctx; //获取该模块的上下文,说白了就是获取该模块具体对应的模块,event,http等
/*调用具体事件模块的init函数。
由于Nginx实现了很多的事件模块,比如:epoll,poll,select, kqueue,aio
(这些模块位于src/event/modules目录中)等等,所以Nginx对事件模块进行
了一层抽象,方便在不同的系统上使用不同的事件模型,也便于扩展新的事件
模型。从此过后,将把注意力主要集中在epoll上。
此处的init回调,其实就是调用了ngx_epoll_init函数。module->actions结构
封装了epoll的所有接口函数。Nginx就是通过actions结构将epoll注册到事件
抽象层中。actions的类型是ngx_event_actions_t,位于src/event/ngx_event.h
*/
if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
/* fatal */
exit(2);
}
break;
}
该部分代码用于找到实际使用的事件模块,然后调用该事件模块的init函数,那么这里我们就可以看看epoll模块的init函数ngx_epoll_init(Ngx_epoll_module.c):
static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
ngx_epoll_conf_t *epcf;
//得到当前epoll事件模块的配置文件
epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
/*ep是epoll模块定义的一个全局变量,初始化为-1*/
if (ep == -1) {
//创建epoll,大小为连接数的一半
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_FILE_AIO)
ngx_epoll_aio_init(cycle, epcf);
#endif
}
/*nevents也是epoll模块定义的一个全局变量,初始化为0,能容纳的最多的事件数量*/
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;
/*这里就是将epoll的具体接口函数注册到事件抽象层接口ngx_event_actions上。
具体是上文提到的ngx_epoll_module_ctx中封装的如下几个函数
ngx_epoll_add_event, add an event
ngx_epoll_del_event, delete an event
ngx_epoll_add_event, enable an event
ngx_epoll_del_event, disable an event
ngx_epoll_add_connection, add an connection
ngx_epoll_del_connection, delete an connection
NULL, process the changes
ngx_epoll_process_events, process the events
ngx_epoll_init, init the events
ngx_epoll_done, done the events
*/
//ngx_event_actions 是在events.c里面定义的一个变量,代表当前事件模块的一些动作函数
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;
}
该函数很简单,主要是创建epoll,还有一句代码比较重要,
ngx_event_actions = ngx_epoll_module_ctx.actions;
就是这句代码将epoll模块实际绑定到Nginx当中去的,用变量ngx_event_actions指向epoll模块定义的所有动作,epoll模块定义的actions有如下函数:
ngx_epoll_add_event, /* add an event */ //为某个event添加事件,说白了就是在epoll中修改某个文件描述符关联的事件,为其添加一个事件
ngx_epoll_del_event, /* delete an event */ //与上面的方法相反,是删除事件
ngx_epoll_add_event, /* enable an event */ //将某个event添加到epoll当中
ngx_epoll_del_event, /* disable an event */ //i
ngx_epoll_add_connection, /* add an connection */ //说白了就是将某个connection关联的文件描述符注册到epoll当中去
ngx_epoll_del_connection, /* delete an connection */ //与上面相反,将某个connection的fd从epoll当中移除
NULL, /* process the changes */
ngx_epoll_process_events, /* process the events */ //处理epoll中的所有事件
ngx_epoll_init, /* init the events */ //epoll模块的初始化,最重要的 就是建立epoll
ngx_epoll_done, /* done the events */ //好像不知道是干嘛用的
ngx_event_actions变量定义在Ngx_event.c文件当中,然后我们在Ngx_event.h文件中看到如下的宏定义:
#define ngx_process_changes ngx_event_actions.process_changes
#define ngx_process_events ngx_event_actions.process_events
#define ngx_done_events ngx_event_actions.done
#define ngx_add_event ngx_event_actions.add
#define ngx_del_event ngx_event_actions.del
#define ngx_add_conn ngx_event_actions.add_conn
#define ngx_del_conn ngx_event_actions.del_conn
这样一来我们就不难看出Ngxin是如何将epoll模块绑定进来的了,这里调用的方法实际上调用的就是实际使用的事件模块的方法。在看epoll模块实际定义的方法之前,我们先看看Nginx的事件结构的定义吧:
struct ngx_event_s {
void *data; //指向发生当前事件的connection之类的数据
unsigned write:1;
unsigned accept:1; //用这个域来标记当前的事件是监听端口的accept事件
ngx_event_handler_pt handler; //事件的处理函数
ngx_rbtree_node_t timer; //对应的红黑树的节点
};
由于该结构的定义比较长,所以我就只把比较重要的几个域保留了下来,首先是data域,其实指向的是该包含该事件的connection结构,可以在ngx_get_connection函数中找到如下代码:
rev->data = c; //为当前的读事件的data域指向当前已经分配的connection,这样可以通过相应的事件来定义所属的connection
wev->data = c; //原理与上面相同
然后还有就是accept域,表示当前读事件是监听端口的accept事件,用来与一般读事件区别开来。
还有就是handler域,指向的是该事件的处理函数,还有一个就是timer域,这在设置定时事件的时候会用到,他是一个红黑树节点结构。
好,接下来可以具体讲epoll模块定义的函数了,首先是ngx_epoll_add_connection函数,
//将某个连接加入到epoll当中
static ngx_int_t
ngx_epoll_add_connection(ngx_connection_t *c)
{
struct epoll_event ee;
//这里表示当前连接关心的事件
ee.events = EPOLLIN|EPOLLOUT|EPOLLET;
//这里event的data域的ptr指向的是当前的connection
ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"epoll add connection: fd:%d ev:%08XD", c->fd, ee.events);
//在epoll中直接将文件描述符以及关心的事件注册了就好了
if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
"epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
return NGX_ERROR;
}
c->read->active = 1;
c->write->active = 1;
return NGX_OK;
该函数说白了就是将某个connection加入到epoll当中去,更直白一点就是在epoll当中注册该connection的文件描述符,然后进行一些初始化。这里还需要注意的一句代码是:
//这里event的data域的ptr指向的是当前的connection
ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);
就是这句话将在epoll中注册的事件与具体的connection相关联的,这样在以后就可以根据事件的该域找到对应的connection。
接下来是ngx_epoll_del_connection函数,看字面意思就知道是将某个connection从epoll中移除。
//该函数用于将连接删除
static ngx_int_t
ngx_epoll_del_connection(ngx_connection_t *c, ngx_uint_t flags)
{
int op;
struct epoll_event ee;
if (flags & NGX_CLOSE_EVENT) {
c->read->active = 0;
c->write->active = 0;
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"epoll del connection: fd:%d", c->fd);
op = EPOLL_CTL_DEL;
ee.events = 0;
ee.data.ptr = NULL;
//说白了很简单,就是在epoll当中将该连接的文件描述符删去就行了
if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
"epoll_ctl(%d, %d) failed", op, c->fd);
return NGX_ERROR;
}
c->read->active = 0;
c->write->active = 0;
return NGX_OK;
}
函数还是很简单的,更直白的一点就是调用epoll_ctl函数,将connection的文件描述符从epoll中移除就是了。
接下来是ngx_epoll_add_event函数,其实很简单,就不贴代码了,说白了就还是调用epoll_ctl函数修改某个事件的文件描述符的需要响应的事件就行了。
接下来是ngx_epoll_del_event函数,嗯,这个意思与上面那个函数的意思正好相反。
接下来还有一个最重要的函数ngx_epoll_process_events:
//这里是定义的epoll模块具体处理事件的地方
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, **queue;
ngx_connection_t *c;
/* NGX_TIMER_INFINITE == INFTIM */
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll timer: %M", timer);
//这里是epoll的wait,将得到的事件存到event_list里面,最大的事件量是nevents
/*一开始就是等待事件,最长等待时间为timer;nginx为事件
专门用红黑树维护了一个计时器。后续对这个timer单独分析。
*/
events = epoll_wait(ep, event_list, (int) nevents, timer); //这个超时事件是从红黑树里面获取的,当前最近的超时,这样可以保证epoll的wait能够在合适的时间内返回,保证定义的超时事件可以执行
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;
}
//这个锁貌似是线程的时候用吧。。没有搞懂
ngx_mutex_lock(ngx_posted_events_mutex);
//循环遍历所有产生的事件
for (i = 0; i < events; i++) {
c = event_list[i].data.ptr;
//instance 说白了就是个整形的变量
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 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;
}
/*该事件是一个读事件,并该连接上注册的读事件是active的*/
if ((revents & EPOLLIN) && rev->active) {
if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
rev->posted_ready = 1;
} else {
rev->ready = 1;
}
if (flags & NGX_POST_EVENTS) {
//如果设置了NGX_POST_EVENTS,表示当前worker进程已经获取了锁,那么将获取的事件入队,因为可能是监听端口的accept事件,这里如果是监听端口的accept事件的话,那么该event的accept域会置为1 ,这个是在事件模块的worker进程初始化中会设置的
//这里持有了锁就应该将产生的事件放入队列中,是为了能够在锁释放了以后再处理这些事件,这样可以让别的worker进程能够尽快的获取锁
queue = (ngx_event_t **) (rev->accept ?
&ngx_posted_accept_events : &ngx_posted_events);
ngx_locked_post_event(rev, queue);
} else {
rev->handler(rev);
}
}
wev = c->write;
//如果是写事件,而且相应connection的写事件是激活的
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;
}
if (flags & NGX_POST_THREAD_EVENTS) {
wev->posted_ready = 1;
} else {
wev->ready = 1;
}
if (flags & NGX_POST_EVENTS) {
ngx_locked_post_event(wev, &ngx_posted_events);
} else {
wev->handler(wev);
}
}
}
ngx_mutex_unlock(ngx_posted_events_mutex);
return NGX_OK;
}
这段代码比较长,但是很重要。由于这里面涉及到Nginx事件循环的部分,所以这里就不细讲了。说白了这个函数就是调用epoll_wait函数,然后处理所有的事件。嗯。。就是这样。。具体的以后讲事件循环到额 时候讲吧。。
这样epoll模块的基本内容就写完了。。。