nginx 事件机制

     对于一个服务器模型来说,事件模型是至关重要的,nginx本身的高性能也归功于它的事件模型。一般来说,nginx的事件模型是基于epoll。而epoll中会调用3函数,epoll_create,epoll_ctl,epoll_wait.

   (1) 首先介绍一些相关的数据结构:

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);//只有kqueue用到
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; 
extern ngx_event_actions_t   ngx_event_actions;   //定义全局的 ngx_event_actions
#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
typedef struct {
    ngx_uint_t    connections;
    ngx_uint_t    use;

    ngx_flag_t    multi_accept;
    ngx_flag_t    accept_mutex;

    ngx_msec_t    accept_mutex_delay;

    u_char       *name;

#if (NGX_DEBUG)
    ngx_array_t   debug_connection;
#endif
} ngx_event_conf_t;


typedef struct {
    ngx_str_t              *name;

    void                 *(*create_conf)(ngx_cycle_t *cycle);
    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);

    ngx_event_actions_t     actions;
} ngx_event_module_t;

    使用epoll事件模型时会调用的函数:

ngx_event_module_t  ngx_epoll_module_ctx = {
    &epoll_name,
    ngx_epoll_create_conf,               /* create configuration */
    ngx_epoll_init_conf,                 /* init configuration */

    {
        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 */
    }
};

 

(2)事件模型

      ngx_events_module是一个核心模块,  由它来完成event  module  的初始化.在obj/ngx_modules.c文件中我们只发现了2个event module。分别是ngx_event_core_module和ngx_epoll_module。ngx_event_core_module这个模块在事件模型初始化过程中起着至关重要的作用,而ngx_epoll_module实际上就是底层io模型的实现。事件模型的初始化与http模块类似,由ngx_events_module驱动整个事件模块的解析和初始化,ngx_event_core_module对events块大部分指令的解析保存重要的配置信息。

static ngx_command_t  ngx_events_commands[] = {

    { ngx_string("events"),
      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
      ngx_events_block,  //events模块的入口函数
      0,
      0,
      NULL },

      ngx_null_command
};

事件模型的初始化的入口函数:

  1.  ngx_events_block. 

static char *
ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char                 *rv;
    void               ***ctx;
    ngx_uint_t            i;
    ngx_conf_t            pcf;
    ngx_event_module_t   *m;
//为ctx分配空间,所有的event module的全局配置是一个数组,这里也为它分配空间,同时将存放在ngx_cycle->conf_ctx数组的ngx_events_module的配置结构赋值为ctx ctx = ngx_pcalloc(cf->pool, sizeof(void *)); if (ctx == NULL) { return NGX_CONF_ERROR; } *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *)); //将在ngx_cycle->conf_ctx数组中的存放的ngx_events_moudle的config信息赋值给ctx,也就是event module配置信息的数组 *(void **) conf = ctx; for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_EVENT_MODULE) { continue; } m = ngx_modules[i]->ctx; //调用create_conf回调函数创建配置信息结构 if (m->create_conf) { (*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle); if ((*ctx)[ngx_modules[i]->ctx_index] == NULL) { return NGX_CONF_ERROR; } } } //为解析events块准备,设置解析模块的类型, 备份cf,解析完events块后恢复 pcf = *cf; cf->ctx = ctx; cf->module_type = NGX_EVENT_MODULE; cf->cmd_type = NGX_EVENT_CONF; //解析events块 rv = ngx_conf_parse(cf, NULL); //递归调用,复杂块 //恢复cf *cf = pcf; if (rv != NGX_CONF_OK) return rv; for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_EVENT_MODULE) { continue; } m = ngx_modules[i]->ctx; // 解析完events块,接着调用所有event module的init_conf回调函数初始化模块的配置结构。这里ngx_event_core_module和ngx_epoll_module会对配置结构中尚未初始化的一些属性赋默认值,比如默认使用io模型,也就是use指令的默认值。 if (m->init_conf) { rv = m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]); if (rv != NGX_CONF_OK) { return rv; } } } return NGX_CONF_OK; }

     看到这里,events块已经解析完毕,为什么还没看到epoll.在我们解析完配置文件之后会依次调用init_module和init_process,所以事件模型一定是在这两个时间点初始化的。下面看一下ngx_event_core_moduel的结构:

ngx_module_t  ngx_event_core_module = {
    NGX_MODULE_V1,
    &ngx_event_core_module_ctx,            /* module context */
    ngx_event_core_commands,               /* module directives */
    NGX_EVENT_MODULE,                      /* module type */
    NULL,                                  /* init master */
    ngx_event_module_init,                 /* init module */ //ngx_init_cycle中被调用,初始化共享内存中存放的数据结构,accept锁,链接计数器
    ngx_event_process_init,                /* init process *///在创建worker进程后调用
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};
init_module:
static
ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle) { ....... cl = 128; size = cl /* ngx_accept_mutex */ + cl /* ngx_connection_counter */ + cl; /* ngx_temp_number */ shm.size = size; shm.name.len = sizeof("nginx_shared_zone"); shm.name.data = (u_char *) "nginx_shared_zone"; shm.log = cycle->log; if (ngx_shm_alloc(&shm) != NGX_OK) { return NGX_ERROR; } shared = shm.addr; ngx_accept_mutex_ptr = (ngx_atomic_t *) shared; ngx_accept_mutex.spin = (ngx_uint_t) -1; //  将accept锁放入共享内存,并将其初始化。 if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared, cycle->lock_file.data) != NGX_OK) { return NGX_ERROR; } ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);//接着放入连接计数器 }

init_module的回调函数执行完了,接下来执行init_process回调函数.

3.   ngx_event_process_init函数是在创建完worker进程后调用的,实际的初始化工作的大部分由它完成.

static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
//而对于ecf->accept_mutex字段的判断主要是提供用户便利,可以关闭该功能,因为既然均衡策略也有相应的代码逻辑,难保在某些情况下其本身的消耗也许会得不偿失;当然,该字段默认为1,在配置初始化函数ngx_event_core_init_conf()内,有这么一句:ngx_conf_init_value(ecf->accept_mutex, 1);
if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) { ngx_use_accept_mutex = 1; ngx_accept_mutex_held = 0; ngx_accept_mutex_delay = ecf->accept_mutex_delay; } else { ngx_use_accept_mutex = 0; } }

根据配置信息初始化ngx_use_accept_mutex,这个变量用于指示是否使用accept锁.

//初始化timer
if
(ngx_event_timer_init(cycle->log) == NGX_ERROR) { return NGX_ERROR; }
  for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
            continue;
        }

        if (ngx_modules[m]->ctx_index != ecf->use) {
            continue;
        }

        module = ngx_modules[m]->ctx;

        if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) { //初始化epoll,在lunix下  ngx_epoll_init
            /* fatal */
            exit(2);
        }

        break;
    }

 这端代码完成了事件模型的初始化。遍历所有的事件模块找到通过use设定的事件模块,然后调用该模块init函数来初始化事件模型.默认nginx使用epoll,对应 的模块是ngx_epoll_module,它指定的init函数为ngx_epoll_init函数.该函数会调用epoll_create.

   4.ngx_epoll_init    

static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
       ep = epoll_create(cycle->connection_n / 2);//  ep就是epoll的句柄,初值为-1,所以一启动nginx就是调用epoll_create创建句柄,

         if (nevents < epcf->events) {
                  if (event_list) {
                          ngx_free(event_list);
         }

         //初始化nevents和event_list,epcf->events是由ngx_epoll_module的epoll_events指令设置的。nevents和event_list是要传给epoll_wait的参数,nevents是要监听的事件的最大个数,event_list用于存放epoll返回的事件。

         event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,ycle->log);

         nevents = epcf->events;

         ngx_event_actions = ngx_epoll_module_ctx.actions; //为抽象事件模型赋值

}

 为ngx_event_actions赋值,之后ngx_event_actions指向epoll的事件结构。在此之后,所有的事件操作都可以由一组宏完成,定义在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  

在回到ngx_event_process_init函数 :

为nginx中的链接池分配存储空间,cycle->connection_n是由worker_connection指令设置的。
    cycle->connections =  ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
    if (cycle->connections == NULL) {
        return NGX_ERROR;
    }
    c = cycle->connections;
    //为读事件表分配空间并初始化
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log); if (cycle->read_events == NULL) { return NGX_ERROR; } rev = cycle->read_events; for (i = 0; i < cycle->connection_n; i++) { rev[i].closed = 1; // 初始化是关闭的连接 rev[i].instance = 1; #if (NGX_THREADS) rev[i].lock = &c[i].lock; rev[i].own_lock = &c[i].lock; #endif }
   //为写事件表分配空间并初始化
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log); if (cycle->write_events == NULL) { return NGX_ERROR; } wev = cycle->write_events;

for (i = 0; i < cycle->connection_n; i++) {wev[i].closed = 1;}
i = cycle->connection_n;
    next = NULL;

    do {
        i--;

        c[i].data = next;
        c[i].read = &cycle->read_events[i];
        c[i].write = &cycle->write_events[i];
        c[i].fd = (ngx_socket_t) -1;

        next = &c[i];

#if (NGX_THREADS)
        c[i].lock = 0;
#endif
    } while (i);

初始化链接池,每个链接的读写事件在read_events和write_events数组中的下标和这个连接在connections数组中的下标是一致的。nginx将连接池组织成一种类似链表的结构,获取和 释放连接很方便.

//初始化连接池   一开始时链接池都是空闲的
cycle->free_connections = next; cycle->free_connection_n = cycle->connection_n;
    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {

        c = ngx_get_connection(ls[i].fd, cycle->log);

        if (c == NULL) {
            return NGX_ERROR;
        }

        c->log = &ls[i].log;

        c->listening = &ls[i];
        ls[i].connection = c;

        rev = c->read;

        rev->log = c->log;
        rev->accept = 1;// 将连接的accept设置成1,表示可以接受连接请求

#if (NGX_HAVE_DEFERRED_ACCEPT)
        rev->deferred_accept = ls[i].deferred_accept;
#endif

        if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
            if (ls[i].previous) {

                /*
                 * delete the old accept events that were bound to
                 * the old cycle read events array
                 */

                old = ls[i].previous->connection;

                if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT)
                    == NGX_ERROR)
                {
                    return NGX_ERROR;
                }

                old->fd = (ngx_socket_t) -1;
            }
        }
//在监听到连接的时候,调用ngx_event_accept函数 rev
->handler = ngx_event_accept;//设置读事件的处理函数,上面已经设置了rev->accept = 1,也就是说这是监听套接字, //使用accept锁时,等worker进程抢到accept锁,再加入epoll事件循环中 if (ngx_use_accept_mutex) { continue; } //在不使用accept锁时,直接将事件加入epoll事件循环 if (ngx_event_flags & NGX_USE_RTSIG_EVENT) { if (ngx_add_conn(c) == NGX_ERROR) { return NGX_ERROR; } } else { if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; } }

       最后这部分代码完成的是最重要的功能,为所有监听socket分配连接并初始化。在不使用accept锁的情况会直接将所有的监听socket放入epoll事件循环,而在使用accept锁时worker进程必须获得锁后才能将监听socket加入事件循环,这部分工作在事件主循环中完成。上面就是nginx事件模型初始化的全部过程,我们看到了epoll_create和epoll_ctl,那么epoll_wait在那调用的呢?大家还记得在worker进程的主循环中调用的ngx_process_events_and_timers函数吧,之前介绍过这个函数是用于处理网络io事件的,实际就是epoll_wait被调用的地方。

     5. ngx_process_events_and_timers

 (ngx_use_accept_mutex) {
        if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;

        } else {
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }

            if (ngx_accept_mutex_held) {  //当前是否拥有锁
                flags |= NGX_POST_EVENTS;  //所有发生的事件都将延后处理,先释放锁

            } else {
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }

    在使用accept锁的情况下,需要先获得这个锁才能accept到新的连接,通过调用ngx_trylock_accept_mutex可以抢夺锁抢夺锁并accept连接。ngx_accept_disabled变量用于实现简单的负载均衡,防止连接分配的不均匀。这个变量在ngx_event_accept函数被设置为:ngx_cycle->connection_n/8 - ngx_cycle->free_connection_n,当ngx_accept_disabled大于0时,worker进程将放弃抢夺锁,专心处理已有的连接,并把ngx_accept_disabled变量减1。

     ngx_accept_mutex_held标记指示进程是否获得锁,得到锁的进程会添加NGX_POST_EVENTS标记,这个标记意味着将所有发生的事件放到一个队列中在,等到进程释放锁之后再慢慢处理,因为请求的处理可能非常耗时,如果不体现释放锁的话,会导致其他进程一直获取不到锁,这样accept的效率很低。在ngx_trylock_accept_mutex函数内部会先获取锁,然后再调用ngx_enable_accept_events把所有的监听socket添加到epoll事件循环中,下面看一下这个函数。

static ngx_int_t
ngx_enable_accept_events(ngx_cycle_t *cycle)
{
    ngx_uint_t         i;
    ngx_listening_t   *ls;
    ngx_connection_t  *c;

    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {

        c = ls[i].connection;

        if (c->read->active) {
            continue;
        }

        if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {

            if (ngx_add_conn(c) == NGX_ERROR) {
                return NGX_ERROR;
            }

        } else {
            if (ngx_add_event(c->read, NGX_READ_EVENT, 0) == NGX_ERROR) {
                return NGX_ERROR;
            }
        }
    }

    return NGX_OK;
}

在ngx_event_process_init中对于使用accept锁的情况,没有将监听socket加入epoll事件循环,而是这个函数中完成的。下面继续看ngx_process_events_and_timers函数。

delta = ngx_current_msec;
(void) ngx_process_events(cycle, timer, flags);
delta = ngx_current_msec - delta;

上面代码会调用ngx_process_events,根据上面的宏定义,实际会调用ngx_epoll_preocess_events,这个函数会调用epoll_wait监听事件 。

6. ngx_epoll_process_events

    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();
    }

调用epoll_wait函数,根据flag标签更新时间.

for (i = 0; i < events; i++) {
        c = event_list[i].data.ptr;//事件对应的连接信息ngx_connection_t

        instance = (uintptr_t) c & 1;
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);

        rev = c->read;

        revents = event_list[i].events; //发生的事件
        //处理读事件
        if ((revents & EPOLLIN) && rev->active) {

            if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
                rev->posted_ready = 1;

            } else {
                rev->ready = 1;//epoll设置
            }

            if (flags & NGX_POST_EVENTS)  
// * 使用accept锁,此处会将事件添加到队列中。
 这里根据是不是监听socket放到不同的队列
                queue = (ngx_event_t **) (rev->accept ?  &ngx_posted_accept_events : &ngx_posted_events);
                 // 添加到队列 
                ngx_locked_post_event(rev, queue);

            } else {  //如果没有使用accept锁的话,则直接使用回调函数.
                rev->handler(rev);
            }
        }

        wev = c->write;
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);
            }
        }
}

    接下来遍历所有返回的事件对这些事件进行处理,和正常使用epoll差不多,关于读写事件的处理直接看注释。这就是ngx_epoll_process_events的全过程,下面继续介绍ngx_process_events_and_timers的后面流程。

  //ngx_posted_accept_events是accept事件的队列,这里会依次调用每个事件的handler并将其删除。accept事件的handler就是ngx_event_accept,它是在ngx_event_process_init中为每个监听socket的读事件添加的,用来处理新建连接的初始化
if (ngx_posted_accept_events) { ngx_event_process_posted(cycle, &ngx_posted_accept_events); } //释放accept锁 if (ngx_accept_mutex_held) { ngx_shmtx_unlock(&ngx_accept_mutex); } if (delta) { ngx_event_expire_timers(); } if (ngx_posted_events) { if (ngx_threaded) { ngx_wakeup_worker_thread(cycle); } else { ngx_event_process_posted(cycle, &ngx_posted_events); } }

 

 

 

posted @ 2012-11-29 16:46  风去无痕  阅读(3687)  评论(0编辑  收藏  举报