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值的组成中无
图3:指向指针数组某元素的指针conf.pngNGX_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]
取地址后的值。
所以有如下示意图:
接下来将通过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
未完待续。。
网友评论