Nginx源码学习——配置项结构体与指针

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

图1:未保存任何地址的数组 .png

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:部分数组元素保存了有效地址.png

图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_ctxcycle变量的副本。进入·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_CONFNGX_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_modulengx_set_worker_processes函数处理worker_processes配置项
ngx_events_modulengx_events_block函数处理events{...}块配置项。

并且将conf指针传入被回调函数内。

对于ngx_core_module,观察ngx_core_commands[]内的一系列配置项处理函数,不难得出,conf直接指向其配置项结构体,因此有图4:

图4:数组元素作为指针直接指向配置项结构体.png

对于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_moduleevents子模块的数目;令ctx指向该数组。
第三句:将ctx的值赋给conf指向的数组元素(如图3),也就是令该数组元素内保存的指针指向第二句中分配的指针数组。
因此有如图5:

图5:创建events模块的配置项结构体指针数组

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

图6: events模块.png

在执行守护程序之前,在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

未完待续。。

你可能感兴趣的:(Nginx源码学习——配置项结构体与指针)