在《关于nginx事件模块结构体的详解》这篇文章中,我们讲解nginx的事件模块的整体工作流程,并且着重讲解了组织事件模块的各个方法的作用,本文则主要围绕这整个流程,从源码的角度讲解nginx事件模块的实现细节。
1. ngx_events_block()
—-events配置块解析
nginx在解析nginx.conf
配置文件时,如果当前解析的配置项名称为events
,并且是一个配置块,则会调用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; // 如果存储事件模块配置数据的配置项不为空,说明已经解析过配置项了,因而直接返回 if (*(void **) conf) { return "is duplicate"; } // 这里主要是计算event模块的个数,并且将各个event模块的相对顺序标记在了该模块的ctx_index属性中 ngx_event_max_module = ngx_count_modules(cf->cycle, NGX_EVENT_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; } // 将数组值赋值到conf中,也即关联到核心配置对象ngx_cycle_t中 *(void **) conf = ctx; for (i = 0; cf->cycle->modules[i]; i++) { if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) { continue; } m = cf->cycle->modules[i]->ctx; // 如果当前模块的create_conf()方法不为空,则调用该方法创建存储配置项的结构体 if (m->create_conf) { (*ctx)[cf->cycle->modules[i]->ctx_index] = m->create_conf(cf->cycle); if ((*ctx)[cf->cycle->modules[i]->ctx_index] == NULL) { return NGX_CONF_ERROR; } } } // 这里将*cf结构体进行了复制,临时存储在pcf中,然后初始化当前的*cf结构体的模块相关的参数, // 以进行下一步的解析 pcf = *cf; cf->ctx = ctx; cf->module_type = NGX_EVENT_MODULE; cf->cmd_type = NGX_EVENT_CONF; // 解析events{}配置块中的子配置项 rv = ngx_conf_parse(cf, NULL); // 重新将pcf复制给*cf,以供后面返回使用 *cf = pcf; if (rv != NGX_CONF_OK) { return rv; } // 到这里,说明events{}配置块的配置项都解析完成了,因而这里调用各个模块的init_conf()方法, // 进行配置项的初始化和合并工作 for (i = 0; cf->cycle->modules[i]; i++) { if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) { continue; } m = cf->cycle->modules[i]->ctx; // 如果当前模块的init_conf()不为空,则调用其init_conf()方法初始化配置项 if (m->init_conf) { rv = m->init_conf(cf->cycle, (*ctx)[cf->cycle->modules[i]->ctx_index]); if (rv != NGX_CONF_OK) { return rv; } } } return NGX_CONF_OK; }
ngx_events_block()
方法主要完成的工作有如下几个:
● 调用ngx_count_modules()
方法对事件模块序号进行标记,需要注意的是,这里的排序是针对当前模块在所有事件类型模块中的顺序进行标记,并且将序号保存在各模块的ctx_index
属性中,比如这里的事件类型核心模块ngx_event_core_module
的ctx_index
就为0
;
● 为指针ctx
申请内存空间,并且申请一个数组,将其地址赋值给ctx指针,这里的数组长度就为事件模块的数目。其实这里的数组就是用来保存每个事件模块的配置对象的,当前事件模块在所有事件模块中的相对位置就对应于该数组中的相对位置,这里的相对位置也即前一步中计算得到的ctx_index
;
● 调用各个事件模块的create_conf()
方法创建各自的配置结构体,并且将其保存在ctx
指针指向的数组中;
● 调用ngx_conf_parse()
方法对配置文件继续解析,前面我们已经讲到,ngx_events_block()
方法就是解析到events配置项的时候才调用的,因而这里的ngx_conf_parse()
方法的调用就是继续解析events配置块的子配置项,而该方法调用完成则说明events
配置块里的配置项都已经解析完成;
● 调用各个模块的init_conf()
方法对配置项进行初始化,简单的说,就是,由于在nginx.conf
中只配置了部分配置项的值,而剩余的配置项就由init_conf()
方法来设置默认值;
2. ngx_event_init_conf()
—-检查事件模块配置结构体是否正常创建
在nginx解析完nginx.conf配置文件的所有配置项后(包括前一步中讲解的对events配置项的解析),其就会调用所有核心模块的init_conf()方法对核心模块的配置项进行初始化。这里的核心模块就包括ngx_events_module
,该模块的init_conf()
方法指向的就是这里的ngx_event_init_conf()
方法,该方法本质上并没有做什么工作,只是检查了是否创建了存储事件模块配置项的结构体数组。
如下是ngx_event_init_conf()
方法的源码:
static char *ngx_event_init_conf(ngx_cycle_t *cycle, void *conf) { if (ngx_get_conf(cycle->conf_ctx, ngx_events_module) == NULL) { ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no "events" section in configuration"); return NGX_CONF_ERROR; } return NGX_CONF_OK; }
上面两个方法就是ngx_events_module
核心模块的两个主要的配置方法,可以看到,这个核心模块的主要作用就是创建了一个数组,用于存储各个事件模块的配置结构体的。下面我们来看一下事件核心模块的主要方法。
3. ngx_event_core_create_conf()
—-创建事件核心模块配置结构体
在第1点中我们讲到,解析events配置块的子配置项之前,会调用各个事件模块的create_conf()
方法来创建其使用的存储配置数据的结构体,而后调用ngx_conf_parse()
方法来解析子配置项,接着调用各个事件模块的init_conf()方法初始化各个模块配置数据的结构体。
这里ngx_event_core_module_ctx
就是一个事件类型的模块,其create_conf属性指向的就是ngx_event_core_create_conf()
方法,而init_conf
属性指向的就是ngx_event_core_init_conf()
方法。
这一节我们首先讲解ngx_event_core_create_conf()
方法的实现原理:
static void *ngx_event_core_create_conf(ngx_cycle_t *cycle) { ngx_event_conf_t *ecf; ecf = ngx_palloc(cycle->pool, sizeof(ngx_event_conf_t)); if (ecf == NULL) { return NULL; } ecf->connections = NGX_CONF_UNSET_UINT; ecf->use = NGX_CONF_UNSET_UINT; ecf->multi_accept = NGX_CONF_UNSET; ecf->accept_mutex = NGX_CONF_UNSET; ecf->accept_mutex_delay = NGX_CONF_UNSET_MSEC; ecf->name = (void *) NGX_CONF_UNSET; return ecf; }
可以看到,这里的ngx_event_core_create_conf()
方法本质上就是创建了一个ngx_event_conf_t
结构体,并且将各个属性都设置为未设置状态。
4. ngx_event_core_init_conf()
—-初始化配置结构体
前面我们讲到,在解析完各个子配置项之后,nginx会调用各个事件模块的init_conf()
方法,这里的核心事件模块就是这个ngx_event_core_init_conf()
方法,如下是该方法的源码:
static char * ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf) { ngx_event_conf_t *ecf = conf; #if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL) int fd; #endif ngx_int_t i; ngx_module_t *module; ngx_event_module_t *event_module; module = NULL; #if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL) // 测试是否具有创建epoll句柄的权限 fd = epoll_create(100); if (fd != -1) { // 关闭创建的epoll句柄,并且将module指向epoll模块 (void) close(fd); module = &ngx_epoll_module; } else if (ngx_errno != NGX_ENOSYS) { module = &ngx_epoll_module; } #endif // 这里,如果没有前面判断的模块类型,则默认使用事件模块中的第一个模块作为事件处理模型 if (module == NULL) { for (i = 0; cycle->modules[i]; i++) { if (cycle->modules[i]->type != NGX_EVENT_MODULE) { continue; } event_module = cycle->modules[i]->ctx; if (ngx_strcmp(event_module->name->data, event_core_name.data) == 0) { continue; } module = cycle->modules[i]; break; } } // 如果此时module还是为NULL,则返回异常 if (module == NULL) { ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no events module found"); return NGX_CONF_ERROR; } // 下面的操作主要是判断各个属性是否为初始设置的无效值,如果是,则说明nginx.conf中没有配置 // 关于该属性的配置项,那么这里就会为该属性设置默认值 ngx_conf_init_uint_value(ecf->connections, DEFAULT_CONNECTIONS); cycle->connection_n = ecf->connections; ngx_conf_init_uint_value(ecf->use, module->ctx_index); event_module = module->ctx; ngx_conf_init_ptr_value(ecf->name, event_module->name->data); ngx_conf_init_value(ecf->multi_accept, 0); ngx_conf_init_value(ecf->accept_mutex, 0); ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500); return NGX_CONF_OK; }
ngx_event_core_init_conf()方法的主要做了两件事:
● 选择当前所使用的模块,如果没指定,则默认使用第一个事件模块;
● 初始化事件核心模块的配置结构体的各个属性值为默认值。
5. ngx_event_module_init()
—-核心模块的配置项初始化
对于ngx_event_core_module
模块而言,其还指定了两个方法,一个是用于初始化模块的ngx_event_module_init
()
方法,另一个是用于worker进程执行主循环逻辑之前进行调用的ngx_event_process_init()
方法。
ngx_event_module_init()
方法是在master进程中调用的,其会在解析完nginx.conf文件中的所有配置项之后调用,本质上,该方法的作用就是对当前配置的核心模块(事件模块)进行初始化。
如下是ngx_event_module_init()
方法的源码:
/** * 当前方法的主要作用是申请一块用于存储统计数据的共享内存,然后设置ngx_accept_mutex_ptr、 * ngx_connection_counter、ngx_temp_number等变量的地址,如果开启了slab stat, * 那么还会设置ngx_stat_accepted、ngx_stat_handled、ngx_stat_requests等的地址,以统计