Nginx源码分析之 配置结构1

这里写图片描述
最开始一直无法理解 在解析HTTP main上下文时,ctx->srv_confctx->loc_conf有什么用处,其实仔细一想,也没啥用处,也就是临时存储在HTTP main阶段使用的指令解析结果,到最后反正会merge到下面一层的ctx->srv_conf或者ctx->loc_conf

同样,HTTP server上下文时的ctx->loc_conf也什么没用,暂存一些指令的结果而已,最后还是会merge到location 上下文的ctx->loc_conf

个人认为,Nginx的配置结构非常复杂,比Nginx其他模块的都复杂,想要彻底搞清楚,还需要下功夫。下面是个人的一些理解。

if (cmd->type & NGX_DIRECT_CONF) {
    /*"daemon" "worker_processes"等指令为NGX_DIRECT_CONF指令
     *其实也是NGX_MAIN_CONF指令,但是先判断了NGX_DIRECT_CONF所以走这个分支
     */
    conf = ((void **) cf->ctx)[ngx_modules[i]->index];

} else if (cmd->type & NGX_MAIN_CONF) {
    /*例如"http" "mail" "events" 等指令为 NGX_MAIN_CONF 指令*/
    conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);

} else if (cf->ctx) {
    /*这些指令都是隶属于上层指令之下的指令,比如HTTP的server指令等*/
    confp = *(void **) ((char *) cf->ctx + cmd->conf);

    if (confp) {
        conf = confp[ngx_modules[i]->ctx_index];
        printf("-- else conf:%p\n", conf);
    }
}

NGX_DIRECT_CONFNGX_MAIN_CONF表示是配置文件的最外层指令,不同点在于DIRECTNGX_MAIN_CONF配置的内存操作有差异。

cf->ctx都是在 函数 ngx_init_cycle中通过cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *))获取得到的内存。
所以 DIRECT类型 指令 获取的 conf 是 cycle->conf_ctx[index],而 MAIN 类型指令获取的 conf 是&cycle->conf_ctx[index]

对于DIRECT指令,获取到的conf是自己create_conf返回的内存。 例如 ngx_core_module_create_conf、ngx_regex_create_conf等core module的create_conf注册函数,所以 cmd->set 中直接使用这个conf就行了。

对于MAIN指令,conf取得是 cycle->conf_ctx[index] 的地址而不是里面的值,其实纯MAIN指令,没有create_conf指令,cf->ctx[index]取出来也是NULL,所以取conf = &cf->ctx[index]的目的,就是取二级指针,然后在cmd->set函数中,执行类似*conf = malloc(..)的操作,相当于对cf->ctx[index]进行赋值。

话句话说,MAIN指令,其ctx不可能由create_conf简单的创建出来或者压根不需要在create_conf阶段创建内存,所以需要在cmd->set中自行处理。

至于第三个if分支,处理的是即非MAIN和非DIRECT指令,下面用解析 HTTP块 指令来描述其作用。

以解析HTTP为例,解析函数是ngx_http_block,而其是MAIN类型的指令,所以 第三个参数conf其实是&cycle->conf_ctx->ctx[index]

ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_conf_ctx_t         *ctx;

    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    *(ngx_http_conf_ctx_t **) conf = ctx;
}

我们看到了函数一开始就去申请内存,对 conf 代表的地址进行赋值。
这样cycle->conf_ctx[ngx_http_module.index] 就有值了,是ngx_http_conf_ctx_t类型的内存

ngx_http_conf_ctx_t 有 三个 类型的conf成员组成:

typedef struct {
    void        **main_conf;
    void        **srv_conf;
    void        **loc_conf;
} ngx_http_conf_ctx_t;

对各个成员申请内存,可见,每个 module 都可以有自己的 main_conf、srv_conf、loc_conf。因为每个module不同的指令,都有其想要控制的作用域。

    ctx->main_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    ctx->srv_conf  = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    ctx->loc_conf  = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);

图片来自深入理解Nginx

初始化配置结构内存

    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;
        mi = ngx_modules[m]->ctx_index;

        if (module->create_main_conf) {
            ctx->main_conf[mi] = module->create_main_conf(cf);
            if (ctx->main_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_srv_conf) {
            ctx->srv_conf[mi] = module->create_srv_conf(cf);
            if (ctx->srv_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_loc_conf) {
            ctx->loc_conf[mi] = module->create_loc_conf(cf);
            if (ctx->loc_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }

不同HTTP模块通过create_xx_conf获得的返回值类型不一样。例如对于http core module来说,create_loc_conf返回的是ngx_http_core_loc_conf_t类型;对于http proxy module来说,返回的是ngx_http_proxy_loc_conf_t类型。所以自己添加命令或者从现有命令中取配置时,需要注意处理对应的数据结构。

接下去就是递归解析里面的命令了,我们以解析到 client_header_timeout为例

http {
      client_header_timeout 10;
}
static ngx_command_t  ngx_http_core_commands[] = {
    ......
    { ngx_string("client_header_timeout"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_msec_slot,
      NGX_HTTP_SRV_CONF_OFFSET,
      offsetof(ngx_http_core_srv_conf_t, client_header_timeout),
      NULL },
    ......
}

NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF表示支持配置的作用域,可见不支持在location块中配置。
我们回到文章开始,来看看如何处理在HTTP main作用下client_header_timeout这个命令的。

......
} else if (cf->ctx) {
    confp = *(void **) ((char *) cf->ctx + cmd->conf);

    if (confp) {
        conf = confp[ngx_modules[i]->ctx_index];
        printf("-- else conf:%p\n", conf);
    }

cf->ctx 是在ngx_http_block函数中开辟的ngx_http_conf_ctx_t类型的数据结构。
cf->ctx + cmd->conf 对于client_header_timeout指令而言,就是cf->ctx + NGX_HTTP_SRV_CONF_OFFSET,实际上获取到的是ngx_http_conf_ctx_t->srv_conf,对于HTTP模块,confp不为空(main_conf、srv_conf、loc_conf都开辟内存了),所以 conf = confp[ngx_modules[i]->ctx_index];取到的conf,就是ngx_http_core_srv_conf_t类型的内存,它是在 初始化配置结构内存 时create_srv_conf函数指针所代表的ngx_http_core_create_srv_conf函数中开辟的。

如果出现这种情况:

http {
      client_header_timeout 10;
      server {
            client_header_timeout 5;
      }
}

http main上下文中出现的client_header_timeout命令上面说了,其值存在下面ctx2的内存中:

ngx_http_conf_ctx_t *ctx1 = cycle->conf_ctx[ngx_http_module.index];
ngx_http_core_srv_conf_t * ctx2 = ctx1->srv_conf[ngx_http_core_module.ctx_index]

在http server上下文中出现的client_header_timeout,其存储位置则不一样了

ngx_http_conf_ctx_t *ctx3 = //ngx_http_core_server中新开辟的内存;
ngx_http_core_srv_conf_t * ctx4 = ctx3->srv_conf[ngx_http_core_module.ctx_index];

上面可以看到,ctx3是在函数内部动态申请的,ctx4是我们client_header_timeout 5;指令存储的位置。

可见,同一个命令2个不同上下文,其存储的内存也是不同的。这就要求在解析完成的最后阶段,需要抉择这个server 块 到底需要按照哪个命令的结果,到底是10还是5。这个操作就是merge操作。我们先看看ctx3和ctx4保存在哪里。

在解析server块的时候,我们有如下操作:

    /*cscf就是 ctx4 , ctx 就是 ctx3*/
    cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
    cscf->ctx = ctx;


    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];

    cscfp = ngx_array_push(&cmcf->servers);
    if (cscfp == NULL) {
        return NGX_CONF_ERROR;
    }

    /*把ctx4存入到main_conf中*/
    *cscfp = cscf;

说白了,ctx3 是在函数内部动态申请的一块内存,总得保存在某个地方吧,否则就内存泄漏了,于是执行了cscf->ctx = ctx;,即把ctx3保存在ctx4中,然而ctx4本身也是函数内部动态申请的,所以ctx4就保存在cmcf->servers中,ctx->main_conf = 上一级的main_conf,上一级的main_conf就能从cycle->conf_ctx[index]中取到了。

所以merge操作如下:

    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
    cscfp = cmcf->servers.elts;

    /*每个module都可以有自己的main_conf*/
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;
        mi = ngx_modules[m]->ctx_index;

        /* init http{} main_conf's */

        if (module->init_main_conf) {
            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }

        /*进行merge, cf->ctx 就是 ctx1*/
        rv = ngx_http_merge_servers(cf, cmcf, module, mi);
        if (rv != NGX_CONF_OK) {
            goto failed;
        }
    }

merge总得有2个对象吧,一个current 一个是prev,把prev按照自己的逻辑写到current中。
显然,对于下面这种类型的配置方式,prev是http main上下文保存的内存,current是http server 上下文的内存。

http {
      client_header_timeout 10;
      server {
            client_header_timeout 5;
      }
}

prev就是 ctx2,current就是 ctx4。

怎么获取到ctx2呢,其实上面说了:

ngx_http_conf_ctx_t *ctx1 = cycle->conf_ctx[ngx_http_module.index];
ngx_http_core_srv_conf_t * ctx2 = ctx1->srv_conf[ngx_http_core_module.ctx_index]

而ctx4,就需要从 cmcf 中的servers获取。



static char *
ngx_http_merge_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
    ngx_http_module_t *module, ngx_uint_t ctx_index)
{
    char                        *rv;
    ngx_uint_t                   s;
    ngx_http_conf_ctx_t         *ctx, saved;
    ngx_http_core_loc_conf_t    *clcf;
    ngx_http_core_srv_conf_t   **cscfp;

    cscfp = cmcf->servers.elts;
    ctx = (ngx_http_conf_ctx_t *) cf->ctx;
    saved = *ctx;
    rv = NGX_CONF_OK;

    for (s = 0; s < cmcf->servers.nelts; s++) {

        /* merge the server{}s' srv_conf's */

        /*cscfp[s]就是ctx4cscfp[s]->ctx就是ctx3
        * cscfp[s]->ctx->srv_conf[ctx_index]和cscfp[s]的效果是等价的。所以ctx4是找到了。
        * 现在找ctx1,上面说过,ctx1就是 cf->ctx 中 的 srv_conf[ctx_index]。
        *
        */
        ctx->srv_conf = cscfp[s]->ctx->srv_conf;

        if (module->merge_srv_conf) {
            /*入参2是上层配置,即prev,入参2是里层配置,即current*/
            rv = module->merge_srv_conf(cf, saved.srv_conf[ctx_index],
                                        cscfp[s]->ctx->srv_conf[ctx_index]);
            if (rv != NGX_CONF_OK) {
                goto failed;
            }
        }

       ......
    }

failed:

    *ctx = saved;

    return rv;
}

猜你喜欢

转载自blog.csdn.net/mrpre/article/details/80986181