Linux 2.4之前的内核版本号,Nginx事件驱动的方法是使用poll、select功能。过程必须等待一个事件发生在连接上(接收数据)时间,部连接都告诉内核,由内核找出哪些连接上有事件发生。因为须要把大量连接从用户空间复制到内核空间,所以开销巨大,因此,使用poll、select事件驱动方式。最大并发数量仅仅能达到几千。

Linux 2.6版本号之后加入了epoll函数接口。使得最大并发数量能够达到百万级。

epoll的使用方法例如以下:

  • 调用epoll_create建立一个epoll对象。
  • 调用epoll_ctl向epoll对象中加入连接套接字。

  • 调用epoll_wait收集发生事件的连接。

这样便消除了向内核传递连接和内核遍历连接等耗时的操作。

epoll_create方法创建一个epoll对象。在内存中表现为创建一个evetpoll结构体,该结构体中有两个重要的成员:
  • struct rb_root rbr;          // 一棵红黑树。保存全部通过epoll_ctl加入进来的须要监控的事件
  • struct list_head rdllist;    // 一个双向链表,保存将要通过epoll_wait返回的、满足条件的事件
因为全部的事件都挂在了一棵红黑树上,所以对事件的搜索效率是非常高的。epoll中的每个事件相应一个epitem结构体,包括事件相应的信息。上述几个成员的关系例如以下图所看到的:

读过Linux内核的应该不难理解上述结构,epitem的rdllink、rbn成员分别作为双向链表rdllist和红黑树rbr中的“代理”,使得epitem既可以存在与双向链表中,又可以保存在红黑树中。

当有事件就绪时。rdllist不为空,并通过epoll_wait函数将该链表返回用户空间。


以下来分析在Linux中使用的事件驱动模块ngx_epoll_module。

首先是决定解析哪些配置项的ngx_command_t结构体数组:

typedef struct {
ngx_uint_t events; /* epoll_wait的參数3:一次最多能够返回的事件数 */
ngx_uint_t aio_requests;
} ngx_epoll_conf_t;
static ngx_command_t ngx_epoll_commands[] = {
/* epoll_wait系统调用一次最多能够返回的事件数 */
{ 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相关 */
{ 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_epoll_conf_t中都有相应的成员。


接下来是事件模块通用接口ngx_event_module_t。ngx_epoll_module的通用接口定义例如以下:
static ngx_str_t      epoll_name = ngx_string("epoll");
ngx_event_module_t ngx_epoll_module_ctx = {
&epoll_name, /* "epoll" */
ngx_epoll_create_conf, /* 创建存储配置项的结构体 */
ngx_epoll_init_conf, /* 解析完配置项后调用的函数 */
/* ngx_event_actions_t */
{
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_epoll_init方法在Nginx启动过程中的ngx_event_core_module模块中被调用(參见“ngx_event_core_module模块”),它主要完毕两个工作:
  • 调用epoll_create创建epoll对象。
  • 创建event_list数组接收从内核传过来的事件。
此方法的代码例如以下:
static int                  ep = -1;    // epoll对象描写叙述符
static struct epoll_event *event_list; // 作为epoll_wait的參数。接收从内核传过来的事件
static ngx_uint_t nevents; // 可以返回的事件最大数目,同一时候也是event_list数组大小
/* 在ngx_event_core_module中调用,主要完毕两件事情:
* 1、调用epoll_create方法创建epoll对象
* 2、创建event_list数组用于从内核接收发生的事件
*/
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对象,參数表示须要处理的事件的大致数目
* Linux内核中不处理这个參数
*/
ep = epoll_create(cycle->connection_n / 2);
#if (NGX_HAVE_FILE_AIO)
/* 异步I/O相关 */
ngx_epoll_aio_init(cycle, epcf);
#endif
}
if (nevents < epcf->events) {
if (event_list) {
ngx_free(event_list);
}
/* 初始化event_list数组。数组大小是配置项epoll_events的參数 */
event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events, cycle->log);
}
/* nevents相同是epoll_events配置项的參数 */
nevents = epcf->events;
/* 指明读写I/O的方法 */
ngx_io = ngx_os_io;
/* ngx_event_actions是个全局的ngx_event_actions_t结构体
* 用于存储事件模块的10个函数接口
*/
ngx_event_actions = ngx_epoll_module_ctx.actions;
#if (NGX_HAVE_CLEAR_EVENT)
ngx_event_flags = NGX_USE_CLEAR_EVENT // 使用epoll的边缘触发模式
#else
ngx_event_flags = NGX_USE_LEVEL_EVENT // 使用epoll的水平触发模式
#endif
|NGX_USE_GREEDY_EVENT
|NGX_USE_EPOLL_EVENT;
return NGX_OK;
}

与ngx_epoll_init相反的函数是ngx_epoll_done。它在Nginx退出服务时被调用,主要工作是关闭epoll描写叙述符并释放event_list数组。

接下来分析ngx_epoll_add_event方法,它的主要任务是调用epoll_ctl方法将事件加入到epoll对象中,代码例如以下:
/* 把一个感兴趣的事件加入到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;
/* 每一个事件的data成员都存放着其相应的ngx_connection_t连接 */
c = ev->data;
/* events代表事件类型。在以下设置 */
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
}
/* 依据是否为活跃事件确定是改动还是加入事件 */
if (e->active) {
op = EPOLL_CTL_MOD; /* 改动epoll中的事件 */
events |= prev;
} else {
op = EPOLL_CTL_ADD; /* 加入新事件到epoll中 */
}
/* 设置事件类型 */
ee.events = events | (uint32_t) flags;
/* data的ptr成员指向一个连接,同一时候把最低位设置为instance标志,事件分发程序将这个标志提取出来 */
ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
/* 调用epoll_ctl方法加入或改动事件
* 參数1:epoll对象描写叙述符
* 參数2:表示要运行的操作
* EPOLL_CTL_ADD:加入新事件到epoll中
* EPOLL_CTL_MOD:改动epoll中的事件
* EPOLL_CTL_DEL:删除epoll中的事件
* 參数3:待监听的连接套接字
* 參数4:描写叙述事件的结构体epoll_event
*/
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;
}
/* 改动active标志。表示当前事件是活跃的 */
ev->active = 1;
return NGX_OK;
}

该函数所做的工作基本上都是在设置epoll_ctl所需的參数。然后调用epoll_ctl向epoll对象中加入感兴趣的事件。


同理。其他几个方法:ngx_epoll_del_event、ngx_epoll_add_connection、ngx_epoll_del_connection都是使用epoll_ctl函数对epoll对象进行改动。

ngx_event_actions_t中最后一个函数也是最重要的一个函数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 */
/* 等待获取事件,最长等待时间为timer以保证时间可以得到更新
* 參数1:epoll对象描写叙述符
* 參数2:保存返回的就绪事件数组
* 參数3:可以返回的最大事件数目
* 參数4:最长等待时间
* 返回值:就绪事件个数
*/
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 (events == 0) {
if (timer != NGX_TIMER_INFINITE) {
return NGX_OK;
}
return NGX_ERROR;
}
ngx_mutex_lock(ngx_posted_events_mutex);
/* 遍历本次返回的全部事件 */
for (i = 0; i < events; i++) {
c = event_list[i].data.ptr; /* 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)
continue; /* 以过期,不处理 */
/* 获得事件类型 */
revents = event_list[i].events;
....
/* 假设是读事件且该事件是活跃的 */
if ((revents & EPOLLIN) && rev->active)
{
....
/* 延后处理这批事件 */
if (flags & NGX_POST_EVENTS) {
/* 依据是新连接事件还是普通事件选择不同的队列 */
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;
if ((revents & EPOLLOUT) && wev->active)
{
/* 推断是否过期 */
if (c->fd == -1 || wev->instance != instance)
continue;
....
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;
}

上述代码调用epoll_wait函数收集就绪事件,然后调用事件相应的处理方法ngx_event_t.handler对事件进行处理。也就是分发事件。

参考:
《深入了解Nginx》 P310-P323.

【Nginx】epoll事件驱动模块的更多相关文章

  1. Nginx Epoll事件模型优劣

    L30-31 Epoll 性能优势主要源于它不用遍历 假设有100万个链接 其它事件可能都需要遍历所有链接,而Epoll只要遍历活跃的链接,这样大大提升了效率

  2. 【Nginx】事件和连接

    不同的操作系统相应不同的事件驱动机制.在Linux 2.6之后使用epoll机制.相应的事件驱动模块是ngx_epoll_module.Nginx的ngx_event_core_module模块依据操 ...

  3. Nginx的事件循环

    首先事件循环的起点就是监听端口获取连接,我们可以在ngx_event_core_module模块的ngx_event_process_init函数中看到如下的代码: /* for each liste ...

  4. epoll事件模型

    事件模型 EPOLL事件有两种模型: Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据. Level Triggered (LT) 水平触发只要有数据都会触 ...

  5. 浅析 Nginx 网络事件

    Nginx 是一个事件驱动的框架,所谓事件主要指的是网络事件,Nginx 每个网络连接会对应两个网络事件,一个读事件一个写事件.在深入了解 Nginx 各种原理及在极端场景下的一些错误场景处理时,需要 ...

  6. Linux 学习笔记之 --- epoll 事件模型详解

    epoll 主要采用对已就绪的 fd 进行轮询操作   一.epoll 触发方式 epoll支持 ET 和 LT 两种触发方式 ET(边缘触发):Nginx 就是采用 ET 触发方式,只支持 no-b ...

  7. Nginx 网络事件

    L27-29 应用层(如浏览器等一系列组成的发送get请求) 传输层 系统内核打开一个端口将客户端IP及端口和服务端IP及端口记录下来一并传输到网络层 网络层 打包后到链路层 再到客户端路由器至广域网

  8. nginx&http 第二章 ngx 事件event处理 数据结构

    ngx_event.c :这个文件主要放置Nginx事件event模块的核心代码. 包含:进程事件分发器(ngx_process_events_and_timers).事件模块的模块和配置.模块初始化 ...

  9. 【Nginx】ngx_event_core_module模块

    ngx_event_core_module模块属于事件模块,它是其他事件类模块的基础.它主要完毕下面任务: 创建连接池 决定使用哪些事件驱动机制 初始化将要使用的事件模块 以下分析该模块的代码. ng ...

随机推荐

  1. 从O2O体验活动看华硕平板也“来电”新融合理念

          随着平板电脑的普及,用户对平板的要求也变得越来越高,且这种要求已经逐渐从单纯的性能方面拓展到全方位的功能方面,尤其在通讯.社交.娱乐等层面,平板迫切需要满足用户日益增长的需求.       ...

  2. ubuntu14.04中virtualbox虚拟机无法启动

    近期升级了ubuntu14.04,还是按之前的方法安装了virtualbox(guest系统仍然使用升级之前的镜像文件),可是在启动guest系统时,总是报错,提演示样例如以下: Kernel dri ...

  3. Android获取设备採用的时间制式(12小时制式或24小时制式)

    /** * 获取设备採用的时间制式(12小时制式或者24小时制式) * 注意: * 在模拟器上获取的时间制式为空 */ private void getTime_12_24(Context conte ...

  4. 使用函数指针和多态代替冗长的if-else或者switch-case

    在编程中,if-else和switch-case是很常见的分支结构,很少在程序中不用这些控制语句.但是不能否认,在一些场景下,由于分支结构过分长,导致代码不美观且不容易维护,在<重构>一书 ...

  5. DatePicker的使用

    activity_main.xml: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android ...

  6. nginx.conf 文中描述的配置文件

    ###############################nginx.conf 件里文说明 #user nobody; # user 主模块指令,指令nginx worker 执行用户和用户组(u ...

  7. 从零开始,创建GitHub团队开发环境

    从零开始,创建GitHub团队开发环境 GitHub提供免费的团队环境,不过免费仓库容量是300MB,请大家注意. 申请GitHub个人账号 1. 使用浏览器访问GitHub主页.如果使用IE,尽量不 ...

  8. 苹果iOS手机系统诊断功能是后门吗?

    7月20日,美国知名苹果iOS手机系统侦破专家扎德尔斯基在2014年世界黑客大会(HOPE/X)用幻灯片讲演揭露了苹果手机存在系统级"后门". 为此,7月23日.苹果公司马上做出回 ...

  9. 在word 中复选框划勾或叉的方法

    输入大写字母R.大写字母Q ,然后将字体改为Wingdings 2, 就分离得到带框的勾和叉.

  10. linux常用系统配置命令汇总

    系统配置及查看信息相关命令 # uname -a # 查看内核/操作系统/CPU信息# head -n 1 /etc/issue # 查看操作系统版本# cat /proc/cpuinfo # 查看C ...