【Nginx】epoll事件驱动模块

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.

时间: 2024-10-14 12:08:29

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

Nginx 的 epoll 事件驱动模块

概述 在前面的文章中<Nginx 事件模块>介绍了Nginx 的事件驱动框架以及不同类型事件驱动模块的管理.本节基于前面的知识,简单介绍下在Linux 系统下的 epoll 事件驱动模块.关于 epoll 的使用与原理可以参照文章 <epoll 解析>.在这里直接介绍Nginx 服务器基于事件驱动框架实现的 epoll 事件驱动模块. ngx_epoll_module 事件驱动模块 ngx_epoll_conf_t 结构体 ngx_epoll_conf_t 结构体是保存ngx_ep

nginx学习笔记五(nginx的事件模块定义)

在linux后台服务器开发领域里面,epoll的大名是早有所闻.<深入理解nginx>一书在第9章-事件模块中就详细说明了epoll相关的系统调用是怎么嵌入到nginx的框架中. 下面说明nginx框架下与事件处理相关的一些模块. 一.ngx_events_module ngx_events_module是核心模块中的一种.之前一直不是很明白核心模块的意思,现在想来,事件模块的核心模块应该是第一个启动的与事件相关的模块.这个模块并不会去处理实际的事件业务,而是会去做一些基本的初始化操作.ngx

【Nginx】事件和连接

不同的操作系统对应不同的事件驱动机制,在Linux 2.6之后使用epoll机制,对应的事件驱动模块是ngx_epoll_module.Nginx的ngx_event_core_module模块根据操作系统确定使用哪一个事件驱动模块.事件驱动模块在ngx_module_t的ctx通用接口是ngx_event_module_t,定义如下所示: typedef struct { ngx_str_t *name; // 事件模块名字 // 解析配置项之前调用,创建存储配置项参数的结构体 void *(

一个epoll事件实现的高并发服务/客户端(C语言实现,服务端存储基于hashtable)

代码路径:https://github.com/prophetss/epoll-event 之前实现了一个简单高效的hashtable(点这里),之后一直想利用它再延伸一些功能,后来偶然看到一个hashtable与epoll事件的结合感觉效率很高所以自己尝试着实现了下.大体思想是将epoll接到的每一个服务请求存储到hashtable里来管理,每一个请求都可以设置独立的回调函数.具体可以先看代码,注释已经写得很详细.代码实现了一个简单实例,由于条件有限,client端我是fork了大量子进程来模

Nginx 定时器事件

概述 在 Nginx 中定时器事件的实现与内核无关.在事件模块中,当等待的事件不能在指定的时间内到达,则会触发 Nginx 的超时机制,超时机制会对发生超时的事件进行管理,并对这些超时事件作出处理.对于定时事件的管理包括两方面:定时事件对象的组织形式 和 定时事件对象的超时检测. 定时事件的组织 Nginx 的定时器由红黑树实现的.在保存事件的结构体 ngx_event_t 中有三个关于时间管理的成员,如下所示: struct ngx_event_s{ ... /* 标志位,为1表示当前事件已超

Apache select和Nginx epoll模型区别

部分内容摘自跟老男孩学Linux运维:Web集群实战(运维人员必备书籍) http://oldboy.blog.51cto.com/2561410/1752270 1.select 和epoll模型区别 1.1.网络IO模型概述 通常来说,网络IO可以抽象成用户态和内核态之间的数据交换.一次网络数据读取操作(read),可以拆分成两个步骤:1)网卡驱动等待数据准备好(内核态)2)将数据从内核空间拷贝到进程空间(用户态).根据这两个步骤处理方式不一样,我们通常把网络IO划分成阻塞IO和非阻塞IO.

Nginx epoll模型详解

举个例子:假设进程有10万个TCP连接,且只有几百个连接是有事件需要处理的.那么在每一个时刻进程只需要处理这几百个有事件需要处理的连接即可. 事件:即TCP连接上有数据需要交互. select和poll这样处理的:在某一时刻,进程收集所有的连接.并把所有连接的套接字传给操作系统(这个过程其实是用户态内存到内核态内存的复制),而由操作系统内核寻找这那几百个有事件需要处理的连接并处理,然后返回数据给用户. Note:这个过程需要操作系统把全部的连接处理一边,极大浪费系统资源. 而epoll是这样做的

Apache select与Nginx epoll模型区别

1.select 和epoll模型区别1.1.网络IO模型概述通常来说,网络IO可以抽象成用户态和内核态之间的数据交换.一次网络数据读取操作(read),可以拆分成两个步骤:1)网卡驱动等待数据准备好(内核态)2)将数据从内核空间拷贝到进程空间(用户态).根据这两个步骤处理方式不一样,我们通常把网络IO划分成阻塞IO和非阻塞IO.·阻塞IO.用户调用网络IO相关的系统调用时(例如read),如果此时内核网卡还没有读取到网络数据,那么本次系统调用将会一直阻塞,直到对端系统发送的数据到达为止.如果对

【Nginx】ngx_event_core_module模块

ngx_event_core_module模块属于事件模块,它是其它事件类模块的基础.它主要完成以下任务: 创建连接池 决定使用哪些事件驱动机制 初始化将要使用的事件模块 下面分析该模块的代码. ngx_event_core_module的ngx_command_t数组定义如下: /* ngx_event_core_module对7个配置项感兴趣 */ static ngx_command_t ngx_event_core_commands[] = { /* 一个worker进程的最大TCP连接