现在的位置: 首页 > 综合 > 正文

Nginx的epoll模块

2014年07月04日 ⁄ 综合 ⁄ 共 9096字 ⁄ 字号 评论关闭

在前面的文章我们已经知道,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模块的基本内容就写完了。。。

抱歉!评论已关闭.