leevis.com icon indicating copy to clipboard operation
leevis.com copied to clipboard

nginx配置文件解析

Open vislee opened this issue 9 years ago • 0 comments

前言

最近,又再重复着看nginx的代码。越看越激动,设计的如此精妙。代码写的也非常的好。 nginx是个非常强大的web服务器、反向代理服务器。他的强大之处离不开他的配置文件。那么配置文件是如何解析的呢?又是通过什么数据结构存放的呢?

解析流程

首先,c函数从main开始执行,在main函数调用了ngx_init_cycle函数。在该函数中初始化了一个conf_ctx指针数组用来存放各个模块的配置文件结构体指针,随后又调用了所有NGX_CORE_MODULE模块的create_conf创建核心模块配置结构体。最后通过调用两个方法来解析命令行配置和文件配置指令。这两个函数分别是:ngx_conf_paramngx_conf_parse。前面是解析命令行配置参数,后面是解析文件配置参数。前面的函数最终还是调用了后面的,那我们直接看后面的函数,如果传入配置文件结构体指针不为空,那么打开配置文件读取配置文件指令。最后调用ngx_conf_handler解析配置指令,该方法才是解析配置文件最核心的方法,会根据配置指令遍历所有模块找到能解析该指令的方法,找到后调用对应模块的set方法。

解析细节说明

上述从整体流程介绍了一下解析配置文件的过程。本节通过具体的函数说明一下。

./core/nginx.c:main

    ......
    //根据编译生成的模块数组初始化每个模块的序号index。同时把模块的个数赋值给ngx_max_module
    ngx_max_module = 0;
    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]->index = ngx_max_module++;
    }

    cycle = ngx_init_cycle(&init_cycle);
    if (cycle == NULL) {
        if (ngx_test_config) {
            ngx_log_stderr(0, "configuration file %s test failed",
                           init_cycle.conf_file.data);
        }

        return 1;
    }
    ......

./core/ngx_cycle.c:ngx_init_cycle

    ......
    //为所有模块建立一个指针数组,没个模块根据序号index对应一个指针
    cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
    if (cycle->conf_ctx == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
    ......
    //为核心模块(NGX_CORE_MODULE)分配结构体,具体有哪些核心模块呢?
    //通过grep代码就可以知道。我们不解释了。解释关键的几个就可以说明问题了。
    //第一个模块就是ngx_core_module 在nginx.c文件中。
    //第二个模块是`ngx_events_module` 在ngx_event.c文件中。
    //第三个模块是`ngx_http_module` 在ngx_http.c文件中。
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_CORE_MODULE) {
            continue;
        }
        //指向核心模块的上下文
        module = ngx_modules[i]->ctx;
        //如果上下文中create_conf函数指针不为空,就执行该指针指向的函数,分配核心模块配置结构体。
        //实际上我们要说的这三个模块,只有`ngx_core_module`模块设置了函数。
        if (module->create_conf) {
            rv = module->create_conf(cycle);
            if (rv == NULL) {
                ngx_destroy_pool(pool);
                return NULL;
            }
            //把分配的结构体放到数组的对应下标中。实际上该数组的类型是void,你可以放入任何类型的值。
            //实际上也确实是放入不同类型的值,只不过都是指针,有的是指针的指针的指针而已。
            cycle->conf_ctx[ngx_modules[i]->index] = rv;
        }
    }

看下核心模块上下文定义 core/ngx_conf_file.h

typedef struct {
    ngx_str_t             name;
    void               *(*create_conf)(ngx_cycle_t *cycle);
    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
} ngx_core_module_t;

上面说到只有ngx_core_module模块设置了函数指针,那来看下该函数:

static void *
ngx_core_module_create_conf(ngx_cycle_t *cycle)
{
    ngx_core_conf_t  *ccf;

    ccf = ngx_pcalloc(cycle->pool, sizeof(ngx_core_conf_t));
    if (ccf == NULL) {
        return NULL;
    }

    /*
     * set by ngx_pcalloc()
     *
     *     ccf->pid = NULL;
     *     ccf->oldpid = NULL;
     *     ccf->priority = 0;
     *     ccf->cpu_affinity_n = 0;
     *     ccf->cpu_affinity = NULL;
     */

    ccf->daemon = NGX_CONF_UNSET;
    ccf->master = NGX_CONF_UNSET;
    ccf->timer_resolution = NGX_CONF_UNSET_MSEC;

    ccf->worker_processes = NGX_CONF_UNSET;
    ccf->debug_points = NGX_CONF_UNSET;

    ccf->rlimit_nofile = NGX_CONF_UNSET;
    ccf->rlimit_core = NGX_CONF_UNSET;

    ccf->user = (ngx_uid_t) NGX_CONF_UNSET_UINT;
    ccf->group = (ngx_gid_t) NGX_CONF_UNSET_UINT;

    if (ngx_array_init(&ccf->env, cycle->pool, 1, sizeof(ngx_str_t))
        != NGX_OK)
    {
        return NULL;
    }

    return ccf;
}

我们回到ngx_init_cycle接着往下看。 ngx_cycle.c:ngx_init_cycle

    ......
    ngx_memzero(&conf, sizeof(ngx_conf_t));
    /* STUB: init array ? */
    conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
    if (conf.args == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }

    conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
    if (conf.temp_pool == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }


    conf.ctx = cycle->conf_ctx;
    conf.cycle = cycle;
    conf.pool = pool;
    conf.log = log;
    //注意下面这两个参数,一个是模块类型,一个是指令类型。
    //从核心模块开始解析配置文件
    conf.module_type = NGX_CORE_MODULE;
    conf.cmd_type = NGX_MAIN_CONF;

#if 0
    log->log_level = NGX_LOG_DEBUG_ALL;
#endif
    //解析命令行配置,该函数还是调用了下面解析配置文件的函数。
    if (ngx_conf_param(&conf) != NGX_CONF_OK) {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }
    //解析配置文件,第二个参数是通过命令行传入的配置文件
    if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }
    ......

看下ngx_conf_parse函数的实现。该函数调用了ngx_conf_read_tokenngx_conf_handlerngx_conf_read_token看起来很复杂,其作用就是解析配置文件中的指令,把指令存入cf->args数组中,例如:usr liwq;就会解析成[usr,liwq]。cf->args->elts[0]就是指令,cf->args->elts[1...]是指令的值。感兴趣的童鞋可以自己看一下源码,在此就不说明了。重点说下ngx_conf_handler,这才是解析配置文件核心调度函数。 为什么说是核心调度函数呢?看代码: ngx_conf_file.c

static ngx_int_t
ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
    char           *rv;
    void           *conf, **confp;
    ngx_uint_t      i, found;
    ngx_str_t      *name;
    ngx_command_t  *cmd;

    name = cf->args->elts;

    found = 0;
    // 遍历所有的模块
    for (i = 0; ngx_modules[i]; i++) {

        cmd = ngx_modules[i]->commands;
        if (cmd == NULL) {
            continue;
        }
        //遍历每个模块的每个指令数组,找哪个模块的哪个指令能处理配置文件该指令。
        for ( /* void */ ; cmd->name.len; cmd++) {
            //指令名匹配
            if (name->len != cmd->name.len) {
                continue;
            }

            if (ngx_strcmp(name->data, cmd->name.data) != 0) {
                continue;
            }

            found = 1;
            //模块类型检查
            if (ngx_modules[i]->type != NGX_CONF_MODULE
                && ngx_modules[i]->type != cf->module_type)
            {
                continue;
            }

            /* is the directive's location right ? */
            //指令类型检查
            if (!(cmd->type & cf->cmd_type)) {
                continue;
            }

            if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                  "directive \"%s\" is not terminated by \";\"",
                                  name->data);
                return NGX_ERROR;
            }

            if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) {
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "directive \"%s\" has no opening \"{\"",
                                   name->data);
                return NGX_ERROR;
            }

            /* is the directive's argument count right ? */
            //指令值的个数检查
            if (!(cmd->type & NGX_CONF_ANY)) {

                if (cmd->type & NGX_CONF_FLAG) {

                    if (cf->args->nelts != 2) {
                        goto invalid;
                    }

                } else if (cmd->type & NGX_CONF_1MORE) {

                    if (cf->args->nelts < 2) {
                        goto invalid;
                    }

                } else if (cmd->type & NGX_CONF_2MORE) {

                    if (cf->args->nelts < 3) {
                        goto invalid;
                    }

                } else if (cf->args->nelts > NGX_CONF_MAX_ARGS) {

                    goto invalid;

                } else if (!(cmd->type & argument_number[cf->args->nelts - 1]))
                {
                    goto invalid;
                }
            }

            /* set up the directive's configuration context */
            //根据指令的类型,选择解析出的配置文件存放在哪,其实都是存在cycle->conf_ctx指针数组中,但是存在哪个数组的那一层中是不同的。
            conf = NULL;

            if (cmd->type & NGX_DIRECT_CONF) {
                conf = ((void **) cf->ctx)[ngx_modules[i]->index];

            } else if (cmd->type & NGX_MAIN_CONF) {
                conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);

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

                if (confp) {
                    conf = confp[ngx_modules[i]->ctx_index];
                }
            }
            //调用具体的解析指令函数
            rv = cmd->set(cf, cmd, conf);

            if (rv == NGX_CONF_OK) {
                return NGX_OK;
            }

            if (rv == NGX_CONF_ERROR) {
                return NGX_ERROR;
            }

            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "\"%s\" directive %s", name->data, rv);

            return NGX_ERROR;
        }
    }

    if (found) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "\"%s\" directive is not allowed here", name->data);

        return NGX_ERROR;
    }

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                       "unknown directive \"%s\"", name->data);

    return NGX_ERROR;

invalid:

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                       "invalid number of arguments in \"%s\" directive",
                       name->data);

    return NGX_ERROR;
}

配置文件解析过程有几个地方很关键

其中定义在ngx_init_cycle 函数中,为核心模块分配了结构体,在ngx_conf_handler 函数中引用的。

// 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;
        }
    }

// ngx_conf_handler 部分, 其中 NGX_DIRECT_CONF flag的,都使用了上述分配的结构体。 ctx不为空的情况,需要hanlder函数内部分配结构体。无论是框架分配还是主模块分配,实际上分配的都是一个数组(有的是一个数组,例如event模块。有的是多个数组,例如stream模块和http模块),数组每一项是对应模块的配置的结构体。

            conf = NULL;

            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]; // 数组
                }
            }

            rv = cmd->set(cf, cmd, conf);

例如event core模块,分配的就是一个数组,然后再遍历event所有模块

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";
    }

    /* count the number of the event modules and set up their indices */

    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;
    }

    *(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;

        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;
            }
        }
    }

    ......

例如stream core模块,分配的就是一个结构体,该结构体包含的是2个数组,最后再调用stream模块回调函数分配对应模块的配置结构体,保存到数组中。

static char *
ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char                          *rv;
    ngx_uint_t                     i, m, mi, s;
    ngx_conf_t                     pcf;
    ngx_array_t                    ports;
    ngx_stream_listen_t           *listen;
    ngx_stream_module_t           *module;
    ngx_stream_conf_ctx_t         *ctx;
    ngx_stream_core_srv_conf_t   **cscfp;
    ngx_stream_core_main_conf_t   *cmcf;

    if (*(ngx_stream_conf_ctx_t **) conf) {
        return "is duplicate";
    }

    /* the main stream context */

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

    *(ngx_stream_conf_ctx_t **) conf = ctx;

    /* count the number of the stream modules and set up their indices */

    ngx_stream_max_module = ngx_count_modules(cf->cycle, NGX_STREAM_MODULE);


    /* the stream main_conf context, it's the same in the all stream contexts */

    ctx->main_conf = ngx_pcalloc(cf->pool,
                                 sizeof(void *) * ngx_stream_max_module);
    if (ctx->main_conf == NULL) {
        return NGX_CONF_ERROR;
    }


    /*
     * the stream null srv_conf context, it is used to merge
     * the server{}s' srv_conf's
     */

    ctx->srv_conf = ngx_pcalloc(cf->pool,
                                sizeof(void *) * ngx_stream_max_module);
    if (ctx->srv_conf == NULL) {
        return NGX_CONF_ERROR;
    }


    /*
     * create the main_conf's and the null srv_conf's of the all stream modules
     */

    for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_STREAM_MODULE) {
            continue;
        }

        module = cf->cycle->modules[m]->ctx;
        mi = cf->cycle->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;
            }
        }
    }
    ......

写个类似的c函数模拟这几种情况


int test_nginx_conf(void) {
    void *conf = NULL;
    void ****conf_ctx = malloc(3 * sizeof(void*));
    // NGX_DIRECT_CONF
    conf_ctx[0] = malloc(sizeof(int));
    // use
    conf = ((void **) conf_ctx)[0];
    *(int*)conf = 0;

    // NGX_MAIN_CONF|BLOCK
    conf = &((void **)conf_ctx)[1];
    void ***ctx;
    ctx = malloc(sizeof(void*));
    *ctx = malloc(1 * sizeof(int*));
    *(void **) conf = ctx;
    (*ctx)[0] = malloc(sizeof(int));

    // use
    void  **confp;
    confp = *(void **)ctx;
    conf = confp[0];
    *(int*)conf = 1;

    return 0;
}

vislee avatar Nov 07 '15 09:11 vislee