返回
Featured image of post Nginx - 模块初始化

Nginx - 模块初始化

Nginx 模块初始化

Nginx 都是以模块的形式进行组织的,无论是 Nginx 核心还是第三方模块。

我们按照 Nginx 的约定,可以比较方便地定义模块,详见:http://nginx.org/en/docs/dev/development_guide.html#Modules

不过,现在我们想了解的不是 Nginx 模块的定义/使用,而是 Nginx 本身的模块功能是如何实现的、包含了哪些内容,后续的系列文章将围绕这个目的展开。

而这篇文章的目的是解答以下问题:

  • Nginx 配置是如何解析的?
  • Nginx 模块是如何初始化的?

Nginx 模块结构

以下是涉及的模块相关的数据结构:(不完全,仅后续“Nginx 模块初始化调用过程”中涉及的)

Nginx 模块变量

以下是涉及到的模块变量。

ngx_modules

objs/ngx_modules.c:此文件在执行 ./auto/configure 后生成。

extern ngx_module_t  ngx_core_module;

ngx_module_t *ngx_modules[] = {
    &ngx_core_module,
    ...
}

ngx_module_names

objs/ngx_modules.c:此文件在执行 ./auto/configure 后生成。

char *ngx_module_names[] = {
    "ngx_core_module",
    ...
}

Nginx 模块初始化执行流程

main: core/nginx.c
  |
   \ ngx_preinit_modules: core/ngx_module.c, populate global variable "ngx_modules"
  |
   \ ngx_init_cycle: core/ngx_cycle.c,
        |
         \ ngx_cycle_modules: core/ngx_module.c, copy "ngx_modules" to cycle
        |
         \ create_conf: prioritize core modules
        |
         \ ngx_conf_parse: core/ngx_conf_file.c
            |
             \ ngx_conf_handler: core/ngx_conf_file.c,
                |
                 \ cmd->set: defined by module, processes a directive and stores parsed values into the corresponding configuration
                    - such as:
                    - http/ngx_http.c, stream/ngx_stream.c, event/ngx_event.c, mail/ngx_mail.c
                    - ngx_http_block, ngx_stream_block, ngx_events_block, ngx_mail_block
                    |
                    | (http or stream module)
                     \ create_main_conf
                     \ create_srv_conf
                     \ create_loc_conf: http module only
                     \ preconfiguration
                     \ ngx_conf_parse: continue parsing the configuration in the http{}/stream{} block. (!!!)
                        |
                         \ ngx_http_core_server
                            \ ngx_conf_parse
                               \ ngx_http_core_listen
                               \ ngx_http_core_location
                                  \ ngx_conf_parse
                                     \ ...
                               \ ...
                     \ init_main_conf
                     \ ngx_http_merge_servers: http module only
                        |
                         \ merge_srv_conf
                         \ merge_loc_conf
                     \ merge_srv_conf: stream module only
                     \ postconfiguration:调用 postconfiguration 函数,到这里整个 http block 内的配置指令已经解析完了
                    |
                    | (event module)
                     \ create_conf
                     \ init_conf
                    |
                    | (mail module)
                     \ create_main_conf
                     \ create_srv_conf
                     \ init_main_conf
                     \ merge_srv_conf
        |
         \ init_conf: core modules
        |
         \ ngx_init_modules: core/ngx_module.c
            |
             \ init_module: defined by module
    |
     \ ngx_cycle_post_init
    |
     \ ngx_single_process_cycle: core/nginx.c
        |
         \ init_process: defined by module
         \ exit_process: defined by module
         \ ngx_master_process_exit
            |
             \ exit_master
  • 配置解析时,每获取完一项配置,就遍历模块,比较配置指令名称,匹配后调用相应的 cmd->set 函数。
    • 以 HTTP 模块示例:
      • 解析到 http 指令时,执行 ngx_http_block 函数,函数内部继续调用 ngx_conf_parse 对 http { ... } 块内的配置进行解析;
      • 解析到 server 指令时,执行 ngx_http_core_server 函数,函数内部继续调用 ngx_conf_parse 对 server { ... } 块内的配置进行解析;
      • 解析到 listen 指令时,执行 ngx_http_core_listen 函数进行端口监听 (ngx_http_add_listen) 等动作。
  • cmd->set:处理指令并将解析后的值存储到相应的配置中。
    • 在 NGX_CORE_MODULE 类型的模块“http”的 cmd->set (也就是 ngx_http_block) 中,逐个处理 NGX_HTTP_MODULE 类型的模块的上下文;NGX_HTTP_MODULE 的 cmd 还是和其他模块的一起处理了。
    • 因为有顺序,因此是“http”模块的指令(cmd)处理完以后,再处理其他 NGX_HTTP_MODULE 类型模块的指令。
  • 配置解析完以后,进行模块初始化 ngx_init_modules。

接下来对其中的关键调用过程进行解析。

ngx_preinit_modules

调用:

src/core/nginx.c

    if (ngx_preinit_modules() != NGX_OK) {
        return 1;
    }

定义:

src/core/ngx_module.c

ngx_int_t
ngx_preinit_modules(void)
{
    ngx_uint_t  i;

    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]->index = i;
        ngx_modules[i]->name = ngx_module_names[i];
    }

    ngx_modules_n = i;
    ngx_max_module = ngx_modules_n + NGX_MAX_DYNAMIC_MODULES;

    return NGX_OK;
}

此函数在上一篇的入口函数中调用,预初始化阶段,对所有模块进行编号,并填充名字——从全局变量 ngx_modules 指向全局变量 ngx_module_names。

ngx_cycle_modules

调用:

src/core/ngx_cycle.c

ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
    // ...

    if (ngx_cycle_modules(cycle) != NGX_OK) {
        ngx_destroy_pool(pool);
        return NULL;
    }

    // ...
}

定义:

src/core/ngx_module.c

ngx_int_t
ngx_cycle_modules(ngx_cycle_t *cycle)
{
    /*
     * create a list of modules to be used for this cycle,
     * copy static modules to it
     */

    cycle->modules = ngx_pcalloc(cycle->pool, (ngx_max_module + 1)
                                              * sizeof(ngx_module_t *));
    if (cycle->modules == NULL) {
        return NGX_ERROR;
    }

    ngx_memcpy(cycle->modules, ngx_modules,
               ngx_modules_n * sizeof(ngx_module_t *));

    cycle->modules_n = ngx_modules_n;

    return NGX_OK;
}

在前面 ngx_preinit_modules() 时,已经填充了 ngx_modules。 在这里就从当前 cycle 的内存池中分配一块内存,把前面填充到全局变量 ngx_modules 中的模块信息复制一份,用于当前 cycle,使用 cycle->modules 指针指向。

注:每次 nginx reload 时都会新建一个 cycle,销毁旧 cycle。详见:http://nginx.org/en/docs/dev/development_guide.html#cycle

cmd->set

原型:

char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

在前面把模块信息复制到 cycle 后,便开始配置解析,配置解析时逐个调用各个模块,然后对其支持的指令进行解析存储。

static ngx_int_t
ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
    ...

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

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

    ...
}

以上是关于模块初始化、配置解析的调用过程。 一句话总结:程序启动后会进行一系列初始化,并调用 ngx_conf_parse 来解析配置文件,遇到配置指令就执行对应指令的 set 回调,逐级深入。

相信美好的事情即将发生。
Built with Hugo
Theme Stack designed by Jimmy