nginx epoll 模块

2015-08-02 16:58:48   最后更新: 2015-08-02 17:39:06   访问数量:1202




在 nginx 初始化流程的介绍中,我们已经看到 nginx module 是怎么初始化和加载的,nginx module 提供了抽象的接口,由各个实际的模块实现者去实现相应的接口,当然,事件模块也是如此

 

// struct ngx_event_module_t // 事件模块结构体 {{{ typedef struct { // 模块名称 ngx_str_t *name; // 解析配置前,用于创建存储配置项参数结构体的回调函数 void *(*create_conf)(ngx_cycle_t *cycle); // 解析配置完成后,用于综合处理某些配置项 char *(*init_conf)(ngx_cycle_t *cycle, void *conf); // 对于事件驱动机制,每个事件需要实现的 10 个抽象方法 ngx_event_actions_t actions; } ngx_event_module_t; // }}}

 

 

在事件模块结构体的最后定义了 actions 域,用来描述每个事件所需要实现的 10 个接口

// struct ngx_event_actions_t // 事件驱动函数描述结构 {{{ typedef struct { // 事件添加函数 ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); // 事件删除函数 ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); // 事件启用 ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); // 事件禁用 ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); // 向事件添加连接 ngx_int_t (*add_conn)(ngx_connection_t *c); // 从事件中移除一个连接 ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags); // 线程模式下分发事件 ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait); // 分发事件 ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags); // 初始化事件驱动模块 ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer); // 退出事件驱动模块前调用的方法 void (*done)(ngx_cycle_t *cycle); } ngx_event_actions_t; // }}}

 

这十个接口定义了事件启用、禁用;添加、删除;连接的添加、删除;事件的分发、初始化、退出等一系列必要的接口,ngx_event_module_t 类型的 EPOLL 模块实现了相应的方法

 

// ngx_event_module_t ngx_epoll_module_ctx // epoll 模块配置,相应接口的实现 {{{ ngx_event_module_t ngx_epoll_module_ctx = { &epoll_name, // 配置创建回调 ngx_epoll_create_conf, /* create configuration */ // 配置初始化回调 ngx_epoll_init_conf, /* init configuration */ // ngx_event_actions_t 类型,包含全部回调函数 { // add,事件添加 ngx_epoll_add_event, /* add an event */ // del,事件删除 ngx_epoll_del_event, /* delete an event */ // enable,事件启用 ngx_epoll_add_event, /* enable an event */ // disable,事件禁用 ngx_epoll_del_event, /* disable an event */ // add_conn,添加连接 ngx_epoll_add_connection, /* add an connection */ // del_conn,删除连接 ngx_epoll_del_connection, /* delete an connection */ // process_changes,线程模式下分发事件 NULL, /* process the changes */ // process_events,分发事件 ngx_epoll_process_events, /* process the events */ // 初始化事件驱动模块 ngx_epoll_init, /* init the events */ // 退出事件驱动模块前调用的方法 ngx_epoll_done, /* done the events */ } }; // }}}

 

 

ngx_epoll_create_conf、ngx_epoll_init_conf 两个函数分别创建和初始化了 epoll 模块的配置信息结构体

// static void * ngx_epoll_create_conf(ngx_cycle_t *cycle) // 创建存储配置项的结构体 ngx_epoll_conf_t *epcf {{{ static void * ngx_epoll_create_conf(ngx_cycle_t *cycle) { ngx_epoll_conf_t *epcf; epcf = ngx_palloc(cycle->pool, sizeof(ngx_epoll_conf_t)); if (epcf == NULL) { return NULL; } epcf->events = NGX_CONF_UNSET; epcf->aio_requests = NGX_CONF_UNSET; return epcf; } // }}}

 

 

// static char * ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf) // 初始化配置信息结构体 {{{ static char * ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf) { ngx_epoll_conf_t *epcf = conf; ngx_conf_init_uint_value(epcf->events, 512); ngx_conf_init_uint_value(epcf->aio_requests, 32); return NGX_CONF_OK; } // }}}

 

 

配置信息结构体:

// struct ngx_epoll_conf_t // epoll 模块配置信息结构体 {{{ typedef struct { ngx_uint_t events; ngx_uint_t aio_requests; } ngx_epoll_conf_t; // }}}

 

定义了最大事件数和异步 IO 的最大请求数

 

// static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer) // epoll 模块初始化 {{{ 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) { // 创建 epoll fd 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) // 初始化异步IO ngx_epoll_aio_init(cycle, epcf); #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; } // }}}

 

 

初始化过程主要做了三件事:

  1. 调用 epoll_create 创建 epoll fd
  2. 为 event_list 分配空间,用于在 epoll_wait 调用中传递内核态事件
  3. 异步 IO 初始化 ngx_epoll_aio_init

 

event_list 就是事件的队列,存储了 epoll_event 类型的事件对象

// struct epoll_event // epoll 事件结构 {{{ struct epoll_event { uint32_t events; // 事件属性 epoll_data_t data; // 事件具体数据 }; // }}}

 

 

epoll_event 结构描述了事件的具体信息,其中,events 是一个 uint32_t 类型的数值,他的取值如下:

// epoll_event 结构 events 域取值 {{{ #define EPOLLIN 0x001 // 连接上有数据可读,包括 tcp 连接关闭时收到 FIN 包 #define EPOLLPRI 0x002 // 连接上有紧急数据可读 #define EPOLLOUT 0x004 // 连接可写 #define EPOLLRDNORM 0x040 // 普通数据可读 #define EPOLLRDBAND 0x080 // 优先级数据可读 #define EPOLLWRNORM 0x100 // 普通数据可写 #define EPOLLWRBAND 0x200 // 优先级数据可写 #define EPOLLMSG 0x400 // 消息队列数据 #define EPOLLERR 0x008 // 连接上发生错误 #define EPOLLHUP 0x010 // 连接被挂起 #define EPOLLRDHUP 0x2000 // 连接远端已经关闭或半关闭 // }}}

 

 

epoll_event 结构的 data 域是一个联合体,他的类型和意义随实际使用情况的不同而不同

// union epoll_data_t // epoll 信息记录联合体 {{{ typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; // }}}

 

在 nginx 的事件模块中,data 域只用来作为指向 ngx_connection_t 类型的连接的指针

 

关于异步 IO 初始化 ngx_epoll_aio_init,可以参看:

nginx 异步 IO 初始化 -- ngx_epoll_aio_init

 

由于 epoll 并没有启用和禁用的功能,所以在 nginx 中,将事件模块的启用、禁用接口与事件的添加、删除实现为了相同接口

 

事件的添加 -- ngx_epoll_add_even

// static ngx_int_t // ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags) // 将事件加入 epoll 监听 {{{ // 三个传入参数分别是:事件对象、nginx 事件属性和 epoll 事件属性 static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags) { int op; uint32_t events, prev; ngx_event_t *e; ngx_connection_t *c; struct epoll_event ee; c = ev->data; // 在 epoll 模块中,事件结构的 data 域是指向连接结构的指针 events = (uint32_t) event; if (event == NGX_READ_EVENT) { e = c->write; prev = EPOLLOUT; #if (NGX_READ_EVENT != EPOLLIN|EPOLLRDHUP) events = EPOLLIN|EPOLLRDHUP; #endif } else { e = c->read; prev = EPOLLIN|EPOLLRDHUP; #if (NGX_WRITE_EVENT != EPOLLOUT) events = EPOLLOUT; #endif } // 如果连接已经存在,则将已经在 epoll 中注册过的 fd 更新,否则加入新的 fd if (e->active) { op = EPOLL_CTL_MOD; events |= prev; } else { op = EPOLL_CTL_ADD; } ee.events = events | (uint32_t) flags; // ev->instance 判断事件是否过期,过期则不处理 // 因为对齐的原因,地址最低位一定是 0,因此用这一位作为 instance 位 ee.data.ptr = (void *) ((uintptr_t) c | ev->instance); ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0, "epoll add event: fd:%d op:%d ev:%08XD", c->fd, op, ee.events); // 将事件加入 EPOLL 队列 if (epoll_ctl(ep, op, c->fd, &ee) == -1) { ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "epoll_ctl(%d, %d) failed", op, c->fd); return NGX_ERROR; } // 将事件设为活跃状态 ev->active = 1; #if 0 ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0; #endif return NGX_OK; } // }}}

 

 

这段代码只做了一个工作,就是调用 epoll_ctl 将事件添加或更新到 epoll 监听队列中

 

从 epoll 中移除事件 -- ngx_epoll_del_event

事件的移除并不是不再监听该 fd,而是将其设置为非活动状态,因此 ngx_epoll_del_event 与 ngx_epoll_add_event 的代码是非常类似的

// static ngx_int_t // ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags) // 从 epoll 中去除事件 {{{ static ngx_int_t ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags) { int op; uint32_t prev; ngx_event_t *e; ngx_connection_t *c; struct epoll_event ee; /* * when the file descriptor is closed, the epoll automatically deletes * it from its queue, so we do not need to delete explicitly the event * before the closing the file descriptor */ if (flags & NGX_CLOSE_EVENT) { ev->active = 0; return NGX_OK; } c = ev->data; if (event == NGX_READ_EVENT) { e = c->write; prev = EPOLLOUT; } else { e = c->read; prev = EPOLLIN|EPOLLRDHUP; } if (e->active) { op = EPOLL_CTL_MOD; ee.events = prev | (uint32_t) flags; ee.data.ptr = (void *) ((uintptr_t) c | ev->instance); } else { op = EPOLL_CTL_DEL; ee.events = 0; ee.data.ptr = NULL; } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0, "epoll del event: fd:%d op:%d ev:%08XD", c->fd, op, ee.events); if (epoll_ctl(ep, op, c->fd, &ee) == -1) { ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "epoll_ctl(%d, %d) failed", op, c->fd); return NGX_ERROR; } ev->active = 0; return NGX_OK; } // }}}

 

 

上面的代码中使用到了一个小技巧,让事件地址或了一下 instance 这个属性,这意味着什么呢?

由于内存地址对齐的原因,地址的最低位地址一定是 0,因此可以用他来记录某个 bool 型的值,这里就使用 instance 这个属性来判断事件是否过期

那么,什么情况下事件会过期呢?

 

假设 epoll 队列中现在有三个待处理的事件,第一个事件处理过程中,我们发现第三个事件同时也没必要处理了,那么怎么办呢?直接在返回的 event_list 中释放该事件的连接,并将其 fd 设为 -1 是否 OK 呢?存在这样一种情况:

当第一个事件的处理过程中关闭了第三个事件的连接,而第二个事件恰好是建立新连接,他有可能会刚好使用已经被释放的第三个事件的连接和 fd,在第三个事件中这个新建立的连接造成了混淆,因此当调用 ngx_get_connection 从连接池中取出连接时,instance 标志会置反,意味着连接已经过期,这样在第三个事件中就不会做相应的处理了

 

连接的添加与移除与事件的添加与移除也非常类似,不同的是创建连接时,其事件一定是没有绑定的状态,因此无需做过多的判断,而与 ngx_epoll_del_event 不同,移除连接即通过 EPOLL_CTL_DEL 移除坚挺的 fd,因此也无需做过多的判断了

 

连接的添加 -- ngx_epoll_add_connection

// static ngx_int_t ngx_epoll_add_connection(ngx_connection_t *c) // 增加连接 {{{ static ngx_int_t ngx_epoll_add_connection(ngx_connection_t *c) { struct epoll_event ee; ee.events = EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP; 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); 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; } // }}}

 

 

连接的移除 -- ngx_epoll_del_connection

// static ngx_int_t // ngx_epoll_del_connection(ngx_connection_t *c, ngx_uint_t flags) // 从 epoll 中移除连接 {{{ static ngx_int_t ngx_epoll_del_connection(ngx_connection_t *c, ngx_uint_t flags) { int op; struct epoll_event ee; /* * when the file descriptor is closed the epoll automatically deletes * it from its queue so we do not need to delete explicitly the event * before the closing the file descriptor */ 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; 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; } // }}}

 

 

参看:

事件驱动函数 -- ngx_process_events_and_timers

 






技术帖      龙潭书斋      io      epoll      nginx      事件      模块      events      aio      异步io      event      ngx_event_t      epoll_ctl      module     


京ICP备15018585号