nginx的代码是以模块的形式组织起来的,当我们执行完configure后会生成ngx_modules.c, 这个文件中记录了nginx的所有模块(包括扩展模块):

ngx_module_t *ngx_modules[] = {
    &ngx_core_module,
    &ngx_errlog_module,
    &ngx_conf_module,
    &ngx_regex_module,
    &ngx_events_module,
    &ngx_event_core_module,
    &ngx_epoll_module,
    &ngx_http_module,
    &ngx_http_core_module,
    &ngx_http_log_module,
    ...
}

我们先来看下nginx的模块结构图:

  • ngx_cycle_t在程序启动时被创建,且是全局唯一的。
  • ngx_cycle_t中的modulesngx_module_t数组,初始值从ngx_modules拷贝。
  • ngx_module_t关联一个或多个ngx_command_t对象,这个对象大有用处,一来它是创建次级模块的入口,二来它负责读取配置。
  • ngx_*_module_t是模块的上下文对象,它用于模块的个性化定制,一般来说,同一类别的模块拥有相同的上下文对象。 如,NGX_CORE_MODULE类别的模块拥有ngx_core_module_t类型的上下文对象;NGX_EVENT_MODULE类别的模块拥有ngx_event_module_t类型的上下文对象。
  • ngx_cytle_t结构体中还有conf_ctx成员,它也是一个数组,初始化为跟modules一样的大小,conf_ctx用于存储模块的配置。

nginx的所有模块都是存放在ngx_cycle_t结构的modules数组中的,在内存中它们是线性的,但在逻辑上它们是分层的。 我们用下面的示意图展示模块的分层结构:

nginx中的模块根据类型字段(ngx_module_t->type)进行了分层、分组:

  • 顶层为NGX_CORE_MODULE类型的模块,它们的上下文对象为ngx_core_module_t类型。 顶层模块可接入二层模块,一般通过ngx_command_t结构中的set方法来接入。 如,NGX_EVENT_MODULE模块组就是通过ngx_events_block方法接入的。
  • 二层NGX_EVENT_MODULE模块组是通过顶层模块ngx_events_module接入的; 二层NGX_HTTP_MODULE模块组是通过顶层模块ngx_http_module接入的。 具体是怎么接入的我们在下面讨论。

上面的分层模块图有些模块是加粗的,这些模块是组织模块,负责本模块组的管理,未加粗的则是实际干活的功能模块。

我们接下来看看,二层模块是如何从顶层模块接入的:

ngx_preinit_modules负责所有模块初始化:

ngx_int_t ngx_preinit_modules()
{
    ngx_uint_t  i;

    ngx_max_module = 0;
    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;
}

cycle->modules[i]->ctx->create_confcycle->modules[i]->ctx->init_conf只针对顶层模块,用来创建和初始化各模块的配置。 ngx_conf_parse读取并解析配置文件,解析完配置后调用ngx_conf_handlerngx_conf_handler遍历模块的命令数组,然后根据配置项与命令参数一一对比,如果符合,则调用命令结构的set函数。 注意,set函数就是接入二层模块的关键

对于不负责接入的功能模块,set函数一般置为设置配置的函数。 比如,ngx_errlog_modulecommands定义为:

static ngx_command_t  ngx_errlog_commands[] = {
    {ngx_string("error_log"),
     NGX_MAIN_CONF|NGX_CONF_1MORE,
     ngx_error_log,
     0,
     0,
     NULL},

    ngx_null_command
};

ngx_error_log定义如下:

static char *ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_log_t  *dummy;
    dummy = &cf->cycle->new_log;
    return ngx_log_set_log(cf, &dummy);
}

而对于需要接入二层模块的顶层模块,主要就是依赖set函数来完成二层模块的接入了。 我们来看下ngx_events_modulecommands成员的定义:

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_EVENT_MODULE模块组就是在ngx_events_block函数中接入的。 为了把二层模块的接入过程阐释清楚,我们完整的展示下ngx_events_block函数的定义。 所有的二层模块的接入过程基本类似,我们自己写nginx的扩展的时候,最好也参照现有方法。

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

    pcf = *cf;
    cf->ctx = ctx;
    cf->module_type = NGX_EVENT_MODULE;
    cf->cmd_type = NGX_EVENT_CONF;

    rv = ngx_conf_parse(cf, NULL);

    *cf = pcf;

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

    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->init_conf) {
            rv = m->init_conf(cf->cycle,
                              (*ctx)[cf->cycle->modules[i]->ctx_index]);
            if (rv != NGX_CONF_OK) {
                return rv;
            }
        }
    }

    return NGX_CONF_OK;
}

函数调用流程如下:

是不是跟ngx_init_cycle中初始化顶层模块的过程很像? NGX_EVENT_MODULE模块组的初始化过程也是三步:

  1. 调用上下文对象中的create_conf方法创建配置
  2. 调用ngx_conf_parse加载事件模块配置
  3. 调用上下文对象中的init_conf完成初始化

不过需要注意的是,事件模块中的上下文对象是ngx_event_module_t结构; 而顶层模块中的上下文对象是ngx_core_module_t结构,虽然它们拥有相同的配置创建和初始化函数,但它们是来自不同的结构体。

最后,我们通过下面的示意图来展示下ngx_cycle_t结构中的模块数组和配置数组,以及二级模块的配置存放方式:


本文作者ruleless, 欢迎评论、交流。
转载请务必标注出处: nginx分层模块设计