nginx源码分析(4)——事件模型

        对于一个服务器来说,事件模型是至关重要的,nginx本身的高性能也要归功于其优秀的事件模型。一般地,nginx的事件模型是基于epoll的,而使用epoll需要调用epoll_create、epoll_ctl和epoll_wait三个函数。由于nginx本身的松耦合模块机制,这些函数的调用被隐藏在很难发现的地方,本篇文章就来介绍nginx的事件模型的初始化过程,从而大家可以清晰的知道epoll各个函数的具体调用位置。

1. 重要的数据结构

        1. ngx_event_actions_t

        这个结构是nginx底层事件模型的抽象,具体的io模型会有自己的实现,比如epoll、select。通过这一层的抽象屏蔽了底层的不同实现,我们可以轻易从一种模型迁移至其他模型。这实际上就是C的面向接口编程,值得学习。在ngx_event.c中定义了全局变量ngx_event_actions,他指向具体的底层实现,在底层模型init函数中被赋值。

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;


2. 事件模型

        在前文中已经介绍过,ngx_events_module是一个core module,由它来完成event module的初始化。当我们查看objs/ngx_modules.c文件中的ngx_moduels数组(保存所有nginx模块)时,会发现只有两个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块大部分指令的解析保存重要的配置信息。下面就来具体看看事件模型的初始化,这里依然采用之前的方式——按照nginx执行的流程,也就是事件模型的初始化顺序。

        1. ngx_events_block

        与ngx_http_module类似,ngx_events_module只有一个指令:events,这是一个块指令,而它的回调函数ngx_events_block就是事件模型初始化的入口。  

    ngx_event_max_module = 0;
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }

        ngx_modules[i]->ctx_index = ngx_event_max_module++;
    }
        还是与http模块一样,对所有的event module计数,同时更新模块的ctx_index,还记得这个变量吗?它就是该模块的配置结构在具体类型(http、event...)配置结构数组中的下标。

 

    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 *));
    if (*ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    /**
     * 将在ngx_cycle->conf_ctx数组中存放的ngx_events_module的config信息赋值为ctx
     * 也就是所有event module配置信息的数组。
     */
    *(void **) conf = ctx;
        为ctx分配空间,所有event module的全局配置就是一个数组,这里也为它分配空间,同时将存放在ngx_cycle->conf_ctx数组的ngx_events_module的配置结构赋值为ctx。


    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }

        m = ngx_modules[i]->ctx;

        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;
            }
        }
    }
        调用所有event module的create_conf回调函数创建配置结构。


    /**
     * 为解析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);
        开始解析events块。前面已经介绍过nginx中只有ngx_event_core_module和ngx_epoll_module两个event module。解析events块就是解析这两个模块的指令,而它们的指令主要是一些配置信息,比如worker_connection指令设置每个worker的连接数,实际上就是为ngx_cycle->connection_n赋值而已,accept_mutex指令就是指定是否使用accept锁,所以这部分内容并不影响事件模块的初始化,所以这不再赘述。

        

    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }

        m = ngx_modules[i]->ctx;

        if (m->init_conf) {
            rv = m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]);
            if (rv != NGX_CONF_OK) {
                return rv;
            }
        }
    }
        解析完events块,接着调用所有event module的init_conf回调函数初始化模块的配置结构。这里ngx_event_core_module和ngx_epoll_module会对配置结构中尚未初始化的一些属性赋默认值,比如默认使用io模型,也就是use指令的默认值。

        看到这里,大家可能好奇events块已经解析完毕,为什么连epoll这个词的影子都没有见到呢?不知大家是否还记得所有nginx模块的结构?也就是ngx_module_t结构,它包含了大量的回调函数,可以在nginx初始化过程的某个时间点被调用。在我们解析完配置文件之后会依次调用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_init_cycle中调用,初始化共享内存中存放的数据结构。
     * accept锁、连接计数器
     */
    ngx_event_module_init,                 /* init module */

    /**
     * 在创建worker进程后调用
     */
    ngx_event_process_init,                /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};
        ngx_event_core_module有ngx_event_module_init和ngx_event_process_init两个回调函数,注释已经注明具体的调用时间点,下面就看一下这个两个函数是如何完成事件模型的初始化的。

        2. ngx_event_module_init

        这个函数是init_module回调,在ngx_init_cycle函数中解析完配置文件后会被调用。这个函数的功能很简单,就是把需要存储在共享内存中的数据的初始化。

    cl = 128;

    size = cl            /* ngx_accept_mutex */
           + cl          /* ngx_connection_counter */
           + cl;         /* ngx_temp_number */
        注释的很清楚,这里共需要存放3个变量,每个变量占用128字节。ngx_accept_mutex是accept锁,用于worker进程在accept连接时防止惊群。ngx_connection_counter连接计数器,记录nginx从启动到目前为止处理的连接的个数。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;

    /**
     * unix下调用mmap创建共享内存
     */
    if (ngx_shm_alloc(&shm) != NGX_OK) {
        return NGX_ERROR;
    }

    shared = shm.addr;
        初始化并创建共享内存,linux下使用mmap系统调用。


    ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
    ngx_accept_mutex.spin = (ngx_uint_t) -1;

    if (ngx_shmtx_create(&ngx_accept_mutex, shared, cycle->lock_file.data)
        != NGX_OK)
    {
        return NGX_ERROR;
    }
        将accept锁放入共享内存,并将其初始化。

    ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);
    (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);
        接着放入连接计数器。这样init_module回调函数执行完了,下面看一下init_process回调函数。


        3. ngx_event_process_init(1)

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

    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锁。


    if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
        return NGX_ERROR;
    }
        初始化timer。


    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) {
            /* fatal */
            exit(2);
        }

        break;
    }
        这段代码完成具体事件模型的初始化。遍历所有event模块找到通过use指令指定的事件模块,然后调用该模块的init函数初始化模型。默认nginx使用epoll,对应的就是ngx_epoll_module,它指定的init函数就是ngx_epoll_init,这个函数会调用epoll_create创建句柄,下面看一下具体流程。

        4. ngx_epoll_init

    if (ep == -1) {
        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;
        }
    }
        ep就是epoll的句柄,初值为-1,所以一启动nginx就是调用epoll_create创建句柄,这里为什么只要监听connection_n/2呢?不懂。。


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

    ngx_io = ngx_os_io;

    // 为抽象事件模型赋值
    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_actions指向的模型完成的。


#if (NGX_HAVE_CLEAR_EVENT)
    ngx_event_flags = NGX_USE_CLEAR_EVENT
#else
    ngx_event_flags = NGX_USE_LEVEL_EVEN
#endif
                      |NGX_USE_GREEDY_EVENT
                      |NGX_USE_EPOLL_EVENT;
        ngx_event_flags表示具体使用的io模型,以及其他的标志。这就完成了对epoll模块的初始化,其中最重要的就是调用epoll_create创建epoll句柄,下面看一下ngx_event_process_init的后续过程。

        5. ngx_event_process_init(2)

    cycle->connections =
        ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
    if (cycle->connections == NULL) {
        return NGX_ERROR;
    }

    c = cycle->connections;
        为ngx_cycle_t中的连接池分配空间,cycle->connection_n是由worker_connection指令设置的。


    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;	// 初始化是关闭的
#if (NGX_THREADS)
        wev[i].lock = &c[i].lock;
        wev[i].own_lock = &c[i].lock;
#endif
    }
        为写事件表分配空间并初始化。


    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和write)在read_events数组和write_events数组的下标和这个连接在connections数组中的下标是一致的。nginx将连接池织成一种类似链表和数组的结构,获取释放连接非常方便,具体见 nginx的connections数组一文。


    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;
        // 将连接的accept设置成1,表示可以接受连接请求
        rev->accept = 1;

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

        if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {	// 没有使用iocp
            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;
            }
        }

#if (NGX_WIN32)

       ……

#else

        /**
         * 设置读事件的处理函数。
         * 上面已经设置了rev->accept = 1,也就是说这是监听套接字,
         * 在监听到连接时调用ngx_event_accept
         */
        rev->handler = ngx_event_accept;

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

#endif

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

        6. ngx_process_events_and_timers(1)

    if (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_
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 (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,根据上面介绍的io操作宏,实际这里调用的是ngx_epoll_process_events,这个函数会调用epoll_wait监听事件。

        7. 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等待事件,并根据flags判断是否需要更新时间。


    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;
    }
        处理异常情况,包括信号中断。

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

        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 {
            	// epoll会设置ready为1
                rev->ready = 1;
            }

            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) {
                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的后面流程。

        8. ngx_epoll_process_events(2)

    if (ngx_posted_accept_events) {
        ngx_event_process_posted(cycle, &ngx_posted_accept_events);
    }
        ngx_posted_accept_events是accept事件的队列,这里会依次调用每个事件的handler并将其删除。accept事件的handler就是ngx_event_accept,它是在ngx_event_process_init中为每个监听socket的读事件添加的,用来处理新建连接的初始化。在处理完所有的accept事件后,必不可少的一步是释放accept锁,让其他worker进程有获取锁的机会,代码如下:

    if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }

   

        这个函数的最后的部分就是其他事件的处理,与处理accpet类似也是调用事件的handler。

    if (ngx_posted_events) {
        if (ngx_threaded) {
            ngx_wakeup_worker_thread(cycle);
        } else {
            ngx_event_process_posted(cycle, &ngx_posted_events);
        }
    }
        在执行完ngx_process_events_and_timers后,会继续worker进程的主循环,然后一直不停的调用ngx_process_events_and_timers,让进程阻塞在epoll_wait上直到有事件发生,这个循环会一直不停,周而复始直到worker进程退出。

        上面在介绍事件模型初始化时,并没有提到监听socket的创建和初始化过程,还有一旦accept到新的连接,那么调用ngx_event_accpet后又会发生什么。这些内容会在后面的源码分析文章中一一介绍。




你可能感兴趣的:(nginx源码分析(4)——事件模型)