Nginx中定义了许多基本数据结构,如双向链表ngx_queue_t、动态数组ngx_array_t等等。也定义了与功能息息相关的复杂的数据结构,其中最核心的有:ngx_cycle_t ngx_module_t ngx_command_t等。这里我们着重分析ngx_cycle_t结构体成员——void ****conf_ctx在 ngx_init_cycle函数执行完成后,其所指向的内存的一系列变化。
conf_ctx是一个指向配置项结构体的指针。之所以有4个****,是因为该指针指向一个存储着指针的数组,这个数组中的指针元素又有可能指向另一个存放指针的数组。具体结构图将在后文中给出。
1. conf_ctx指向指针数组
分配一块连续内存(数组),用于保存ngx_max_module个指针元素,每个指针元素按位置对应一个模块数组中的模块,指向对应于该模块的配置项结构体或者配置项结构体指针数组。ngx_max_module为总的模块个数。cycle->conf_ctx保存数组首地址,代码如下:
cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *)); //ngx_init_cycle()函数
之后的内存结构如图所示,本人通过gdb调试取得,本次conf_ctx地址为:0x101011a28

2.回调核心模块create_conf函数
接下来,遍历模块数组cycle->modules,找到核心模块,回调其create_conf函数,创建配置项结构体,将相应地址存放于图1中的数组内。
摘自ngx_init_cycle函数
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}
module = cycle->modules[i]->ctx;
if (module->create_conf) {
rv = module->create_conf(cycle);
if (rv == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
cycle->conf_ctx[cycle->modules[i]->index] = rv;
}
}
cycle->conf_ctx[cycle->modules[i]->index] = rv;语句的执行结果,如图2所示:

图2与图1对比可见,只有少部分数组元素保存了指向配置项结构体的有效地址。比如cycle->conf_ctx[0]保存了指向ngx_core_module模块的配置项结构体指针,cycle->conf_ctx[4]理应保存指向ngx_event_module模块结构体指针数组的指针,但现在是0x0.
那么这些位置的数组元素是在何时保存有效指针的呢? 答案是:解析配置文件的时候。
3. 解析配置文件,处理配置项及其它
除了解析配置文件,处理配置项,还有可能创建下一级的指针数组。
代码如下所示,:
//`摘自ngx_init_cycle函数`
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
conf实参中保存了conf_ctx和cycle变量的副本。进入·ngx_conf_parse·函数内部,略过诸多细节,看关键性代码:
回调模块自身注册的配置处理函数,
rv = (*cf->handler)(cf, NULL, cf->handler_conf); //cf 即 conf实参
或者,调用通用的配置处理函数:
rc = ngx_conf_handler(cf, rc);
假设调用ngx_conf_handler函数,进入该函数内部:
if (cmd->type & NGX_DIRECT_CONF) {
conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];
} else if (cmd->type & NGX_MAIN_CONF) {
conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]);
} else if (cf->ctx) {
confp = *(void **) ((char *) cf->ctx + cmd->conf);
if (confp) {
conf = confp[cf->cycle->modules[i]->ctx_index];
}
}
解析一下上面的代码。
conf是该函数定义的ngx_conf_t类型的局部变量,cmd变量的类型是ngx_command_t。
我们知道,定义一个模块,首先就是实现ngx_module_t结构体,该结构体内就有一个ngx_command_t数组成员,该成员决定了该模块对那些配置项感兴趣,以及如何处理自己感兴趣的配置项,换言之——定制功能。
举例1——ngx_events_module模块,其ngx_command_t数组成员如下:
static ngx_command_t ngx_events_commands[] = {
{ ngx_string("events"), // 配置项名称
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, //配置项类型
ngx_events_block, //处理配置项的函数
0,
0,
NULL },
ngx_null_command
};
可以看到,ngx_events_module模块只对event{...}块配置项感兴趣,并且使用ngx_events_block函数处理该配置项。
举例2——ngx_core_module模块,其ngx_command_t数组成员如下:
static ngx_command_t ngx_core_commands[] = {
......
......
{ ngx_string("worker_processes"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
ngx_set_worker_processes,
0,
0,
NULL },
......
......
}
在cmd->type的取值中: NGX_MAIN_CONF 和 NGX_DIRECT_CONF功能很难用语言清晰总结,我们直接看其功能:
当
cmd->type值的组成中有NGX_DIRECT_CONF参与时(比如ngx_core_module模块),局部变量conf取值如下:
conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index];
cf->ctx 也就是前文中conf_ctx的副本,cf->cycle->modules[i]->index是模块在模块数组中的索引,前文中已经描述了模块数组与conf_ctx所指向数组的一一对应关系。
由于void*不能被解引用,所以这里转换为void **,假设是cf->cycle->modules[i]得到是ngx_core_module模块,那么有index==0,因此等价于:
conf = conf_ctx[0];//即 0x0000000101012580,如图2所示-
当cmd->type值的组成中无
NGX_DIRECT_CONF参与,有NGX_MAIN_CONF参与时(如ngx_events_module模块),局部变量conf取值如下:
conf =& (((void **) cf->ctx)[cf->cycle->modules[i]->index]);
对于ngx_events_module模块,已知cf->cycle->modules[i]->index == 4(如图2),那么对conf就是对conf_ctx[4]取地址后的值。
所以有如下示意图:
图3:指向指针数组某元素的指针conf.png
接下来将通过ngx_command_t的函数指针set,回调单个配置项处理函数。如:
ngx_core_module的ngx_set_worker_processes函数处理worker_processes配置项
ngx_events_module 的ngx_events_block函数处理events{...}块配置项。
并且将conf指针传入被回调函数内。
对于ngx_core_module,观察ngx_core_commands[]内的一系列配置项处理函数,不难得出,conf直接指向其配置项结构体,因此有图4:

对于ngx_events_module模块,分析其ngx_events_block函数:
ctx = ngx_pcalloc(cf->pool, sizeof(void *));
...
*ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
...
*(void **) conf = ctx;
第一句:为指针ctx分配了内存空间
第二句:分配一个ngx_event_max_module大小的指针数组,ngx_event_max_module是events子模块的数目;令ctx指向该数组。
第三句:将ctx的值赋给conf指向的数组元素(如图3),也就是令该数组元素内保存的指针指向第二句中分配的指针数组。
因此有如图5:

接下来,执行过程类似于本文中的2、3步,该函数内部又将调用create_conf创建事件子模块配置项结构体,正如开始时创建核心模块配置项结构体那般,并把地址保存于图5中下一个指针数组中的相应位置,然后调用ngx_conf_parse函数解析配置文件,处理配置项。
结果如图6所示:

在执行守护程序之前,在gdb中查看内存:x/100xg ngx_cycle->conf_ctx,打印信息如下,可见仍有许多单元没有被存入有效地址。
0x101011a28: 0x0000000101012580 0x0000000000000000
0x101011a38: 0x0000000000000000 0x0000000101012678
0x101011a48: 0x00000001010132f0 0x0000000000000000
0x101011a58: 0x0000000000000000 0x0000000101013430
0x101011a68: 0x0000000000000000 0x0000000000000000
0x101011a78: 0x0000000000000000 0x0000000000000000
0x101011a88: 0x0000000000000000 0x0000000000000000
0x101011a98: 0x0000000000000000 0x0000000000000000
0x101011aa8: 0x0000000000000000 0x0000000000000000
0x101011ab8: 0x0000000000000000 0x0000000000000000
0x101011ac8: 0x0000000000000000 0x0000000000000000
0x101011ad8: 0x0000000000000000 0x0000000000000000
0x101011ae8: 0x0000000000000000 0x0000000000000000
未完待续。。
