技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> Apache --> Nginx事件驱动的初始化

Nginx事件驱动的初始化

浏览:1137次  出处信息

Nginx的高性能应该算是事件驱动的功劳。Nginx事件处理的相关代码位于src/event目录中,事件驱动是Nginx的核心,所以代码量相对也比较大。事件驱动初始化的过程主要由下图中的三步组成。

第一步:解析配置文件的初始化

在Nginx的启动初始化过程中,将调用ngx_conf_parse()解析配置文件,此过程将遇到类似如下的配置项:
events {
worker_connections 20480;
}
此处的events是一个block指令,在它下面还可以配置很多其他的指令,比如这里的worker_connections等。events下面可以配置的指令定义在数组ngx_event_core_commands(位于src/event/ngx_event.c文件中)中。每个指令都有自己对应的回调函数,events指令的回调函数是ngx_events_block()(位于src/events/ngx_event.c文件中),即在解析配置文件到events指令的时候,将调用此回调函数。此函数的大概分析如下:

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;
	/* 这个源码中的英文注释已经很清楚了。*/
    /* count the number of the event modules and set up their indices */
    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++;
    }
    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;
    }
    *(void **) conf = ctx;
	/* 循环调用每个事件模块的create_conf函数,创建配置结构*/
    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;
            }
        }
    }
    pcf = *cf;
    cf->ctx = ctx;
    cf->module_type = NGX_EVENT_MODULE;
    cf->cmd_type = NGX_EVENT_CONF;
	/* 由于events是一个block指令,events域下还可以配置很多其他指令,
	比如use等,现在开始解析events block中的指令,完成初始化工作。*/
    rv = ngx_conf_parse(cf, NULL);
    *cf = pcf;
    if (rv != NGX_CONF_OK)
        return rv;
	/*循环执行每个事件模块的init_conf函数,初始化配置结构*/
    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;
            }
        }
    }
    return NGX_CONF_OK;
}

ngx_events_block()函数中最重要的一个过程就是调用ngx_conf_parse(cf, NULL),此处调用ngx_conf_parse()的作用就是完成配置文件中events{}这个block的解析,从而调用其下所有的配置指令的回调函数,完成解析配置文件的初始化工作。

第二步:ngx_event_module_init

在《Nginx启动初始化过程(二)》结尾的时候有这样的一段文字――“执行所有模块的init_module操作,看名字为对模块进行初始化。 浏览源码,发现包括几个NGX_CORE_MODULE类型的模块在内的绝大多数模块都没有这个init回调函数。究竟哪些模块才使用这个回调接口呢?动用搜索功能,终于找到了一个模块使用了这个回调接口,它就是ngx_event_core_module。在此,就不纠结这个独特的初始化函数了,到分析事件驱动的时候,再回头看看。”;此处的小标题ngx_event_module_init就是模块ngx_event_core_module的那个回调函数。ngx_event_module_init的代码分析如下:

static ngx_int_t
ngx_event_module_init(ngx_cycle_t *cycle)
{
    void              ***cf;
    u_char              *shared;
    size_t               size, cl;
    ngx_shm_t            shm;
    ngx_time_t          *tp;
    ngx_core_conf_t     *ccf;
    ngx_event_conf_t    *ecf;

    cf = ngx_get_conf(cycle->conf_ctx, ngx_events_module);
    if (cf == NULL) {
        return NGX_ERROR;
    }
	/*取得ngx_event_core_module模块的配置结构*/
    ecf = (*cf)[ngx_event_core_module.ctx_index];
	/*取得ngx_core_module模块的配置结构*/
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
	/*从ngx_core_module模块的配置结构中获取timer_resolution指令的配置参数*/
    ngx_timer_resolution = ccf->timer_resolution;
	。。。。。。。。。。。。。。。。。。。。
	/*如果关闭了master进程,就不需要下面的步骤了。因为,关闭了master进程就是
	单进程方式工作,下面的步骤是创建共享内存实现锁等工作,单进程不需要。
	*/
    if (ccf->master == 0) {
        return NGX_OK;
    }
	/*已经存在accept互斥体了,不需要再重复创建*/
    if (ngx_accept_mutex_ptr) {
        return NGX_OK;
    }

    /* cl should be equal or bigger than cache line size */
    cl = 128;
	/*后面将会创建size大小的共享内存,这块共享内存将被均分成三段,
	分别供ngx_accept_mutex、ngx_connection_counter、ngx_temp_number
	使用。
	*/
    size = cl            /* ngx_accept_mutex */
           + cl          /* ngx_connection_counter */
           + cl;         /* ngx_temp_number */
	。。。。。。。。。。。。。。。。。。。。。
	/*开始着手创建共享内存,大小为size,命名为nginx_shared_zone*/
    shm.size = size;
    shm.name.len = sizeof("nginx_shared_zone");
    shm.name.data = (u_char *) "nginx_shared_zone";
    shm.log = cycle->log;
	/*创建起共享内存,共享内存的起始地址保存在shm.addr*/
    if (ngx_shm_alloc(&shm) != NGX_OK) {
        return NGX_ERROR;
    }
	/*取得共享内存的起始地址*/
    shared = shm.addr;

	/*accept互斥体取得共享内存的第一段cl大小内存*/
    ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;

	/*创建accept互斥体。

	accept互斥体的实现依赖是否支持原子操作,如果有相应的原子操作;
	就是用取得的这段共享内存来实现accept互斥体;否则,将使用文件
	锁来实现accept互斥体。

	accept互斥体的作用是:避免惊群和实现worker进程的负载均衡。
	*/
    if (ngx_shmtx_create(&ngx_accept_mutex, shared, cycle->lock_file.data)
        != NGX_OK)
    {
        return NGX_ERROR;
    }
	/*ngx_connection_counter取得共享内存的第二段cl大小内存*/
    ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);
    (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);

	/*ngx_temp_number取得共享内存的第三段cl大小内存*/
    ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl);

	。。。。。。。。。。。。。。。。。。。。。。
    return NGX_OK;
}

第三步:ngx_event_process_init

在worker进程的分析中,有提到调用每个模块自定义的进程初始化函数。ngx_event_process_init回调函数就是ngx_event_core_commands模块自定义的进程初始化函数。因此,在master进程创建好了一个worker进程后,worker进程首先就会做进程的初始化工作,此时就会调用ngx_event_process_init函数。ngx_event_process_init的代码分析如下:

static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
    ngx_uint_t           m, i;
    ngx_event_t         *rev, *wev;
    ngx_listening_t     *ls;
    ngx_connection_t    *c, *next, *old;
    ngx_core_conf_t     *ccf;
    ngx_event_conf_t    *ecf;
    ngx_event_module_t  *module;
	/*获取相应模块的配置结构*/
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
	/*master进程打开,worker进程数大于1,配置了accetp_mutex(默认使用)
	时,才使用accept互斥体。
	*/
    if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
        ngx_use_accept_mutex = 1; 	/* 1 使用accept互斥体, 0 不使用*/
        ngx_accept_mutex_held = 0;	/* ngx_accept_mutex_held代表是否获得accept互斥体*/
        ngx_accept_mutex_delay = ecf->accept_mutex_delay; /*抢互斥体失败后,下次再抢的间隔时间*/
    } else {
        ngx_use_accept_mutex = 0;
    }
	/*初始化计时器,此处将会创建起一颗红黑色,来维护计时器。*/
    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; /*非NGX_EVENT_MODULE跳过*/
        }
        if (ngx_modules[m]->ctx_index != ecf->use) {
            continue;/*非use配置指令指定的模块跳过,Linux默认epoll*/
        }
        module = ngx_modules[m]->ctx;
		/*调用具体事件模块的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; 	/*跳出循环,只可能使用一个具体的事件模型*/
    }
	。。。。。。。。。。。。。。。。。。。。。。。。。
	/*创建一个connection数组,维护所有的connection;
	本过程已经是在worker进程中了,所以是每个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 connection_n; i++) {
        rev[i].closed = 1;
        rev[i].instance = 1;
    }

	/*创建一个写事件数组*/
    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 connection_n; i++) {
        wev[i].closed = 1;
    }
    i = cycle->connection_n;
    next = NULL;

	/*初始化整个connection数组,connection数组使用得很是巧妙,
	能够快速的获取释放一个连接结构。下一篇画个图来详细看看
	这个connection。
	*/
    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];
    } while (i);
    cycle->free_connections = next;
    cycle->free_connection_n = cycle->connection_n;
    /* for each listening socket */

	/*为每个监听套接字从connection数组中分配一个连接,即一个slot*/
    ls = cycle->listening.elts;
    for (i = 0; i listening.nelts; i++) {
		/*从connection中取得一个新的连接slot*/
        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*/
	。。。。。。。。。。。。。。。。。。。。
#if (NGX_WIN32)
        。。。。。。。。。。。。。。。。。。。
#else
		/*注册监听套接口读事件的回调函数ngx_event_accept*/
        rev->handler = ngx_event_accept;
		/*使用了accept_mutex,暂时不将监听套接字放入epoll中
		而是等到worker抢到accept互斥体后,再放入epoll,避免
		惊群的发生。
		*/
        if (ngx_use_accept_mutex) {
            continue;
        }
        if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
            if (ngx_add_conn(c) == NGX_ERROR) {
                return NGX_ERROR;
            }
        } else {
        	/*没有使用accept互斥体,那么就在此处将监听套接字放入
        	epoll中。
        	*/
            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
                return NGX_ERROR;
            }
        }
#endif
    }
    return NGX_OK;
}

至此,事件驱动的初始化三步曲就分析完了。

QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1