3

源文件路径

src\core\ngx_conf_file.h
src\core\ngx_conf_file.c

主要内容

本篇的主要目的在于分析Nginx的配置功能。由于Nginx的配置基本就是对模块的配置,因此,在讨论配置功能之前,需要先分析Nginx的模块功能。

对于模块功能,这里的重点不在于某个模块的细节,而在于分析Nginx的模块架构是如何设计的。

类似地,因为对于Nginx的配置文件,没有资料比官方文档上说得更明白,所以,这里的重点并不是对配置文件,而是分析Nginx的配置功能是如何设计的。

好了,开始吧。

模块功能

Nginx的基础架构是高度模块化的。可以从多个方面来观察Nginx的模块化。

官方文档首页的整个下半页是Nginx自身所有模块的列表。从这里可以看到Nginx的模块轮廓。

如果从源码安装Nginx,那么首先执行的configure脚本的配置参数里,很大一部分是用来管理是否安装某个模块的。当然,Nginx运行必备的模块是必须安装的,因此,并不会出现在configure的配置选项里。

Nginx本身由多个基本的模块构成,其中,核心的部分是一个叫ngx_core_module的模块。当然,对于一个web服务器,仅仅有一个核心是不够的,还需要大量的“辅助模块”。这有点像Linux的设计,一堆外围设施作为模块与Linux内核构成整个Linux系统。

很自然的会想到,Nginx的这种模块化设计是支持第三方插件的。这种设计也大大增加了Nginx的弹性和能力。

模块的组织和管理

既然Nginx是由许多模块构成的,那么,如何组织和管理这些模块是首先要关注的问题。在Nginx中,使用全局数组ngx_modules保存和管理所有Nginx的模块

如何做到的呢?

首先,Nginx的众多模块被分成两类:必须安装的模块可以安装的模块

  • 必须安装的模块是保证Nginx正常功能的模块,没得选择,这些模块会出现在ngx_modules里。比如ngx_core_module
  • 可以安装的模块通过configure的配置和系统环境,被有选择的安装,这些模块里,被选择安装的模块会出现在ngx_modules数组中。

configure执行结束后,会生成一个objs\ngx_modules.c的源代码文件:
文件内容随configure配置不同而不同,例如:

#include <ngx_config.h>
#include <ngx_core.h>

extern ngx_module_t  ngx_core_module;
extern ngx_module_t  ngx_errlog_module;
extern ngx_module_t  ngx_conf_module;
extern ngx_module_t  ngx_events_module;
extern ngx_module_t  ngx_event_core_module;
extern ngx_module_t  ngx_epoll_module;
extern ngx_module_t  ngx_regex_module;
extern ngx_module_t  ngx_http_module;
extern ngx_module_t  ngx_http_core_module;
extern ngx_module_t  ngx_http_log_module;
extern ngx_module_t  ngx_http_upstream_module;
extern ngx_module_t  ngx_http_static_module;
extern ngx_module_t  ngx_http_autoindex_module;
extern ngx_module_t  ngx_http_index_module;

...

ngx_module_t *ngx_modules[] = {
    &ngx_core_module,
    &ngx_errlog_module,
    &ngx_conf_module,
    &ngx_events_module,
    &ngx_event_core_module,
    &ngx_epoll_module,
    &ngx_regex_module,
    &ngx_http_module,
    &ngx_http_core_module,
    &ngx_http_log_module,
    &ngx_http_upstream_module,
    &ngx_http_static_module,
    &ngx_http_autoindex_module,
    &ngx_http_index_module,
   ...
    NULL
};

由于数组存放的是同一类型的数据。因此,必须有一个模块基类来抽象的定义所有模块。这个抽象基类就是ngx_module_t

因此,可以看到ngx_modules数组里存放的是ngx_module_t *类型指针,并且,最后一个一定是空指针NULL

将最后一个元素置为空指针NULL,是为了遍历数组的时候,标记数组结束,这样不必在使用时记录数组大小。

例如:

for (i = 0; ngx_modules[i]; i++) {
    ...
}

使用extern ngx_module_t ngx_core_module;使编译器在编译的时候不去处理ngx_core_module,在链接阶段,再将ngx_core_module链接到正确的地址中。

类似地,在源文件core\ngx_conf_file.h的末尾声明外部ngx_modules[]

extern ngx_module_t  *ngx_modules[];

由于core\ngx_conf_file.h会被当作公共头文件被包含到其他文件中,因此,ngx_modules是一个全局变量

======分割线=======
在编译阶段,objs\ngx_modules.c文件中的那一堆extern变量及ngx_modules变量都会被初始化。原因在于,这些变量都属于全局变量,将会被静态初始化
======分割线=======

这样,有了全局数组ngx_modules,就可以获取所有模块的指针,从而操作所有模块。对所有模块的管理和操作也由此展开。

所有模块的基类 ngx_module_t

所有模块都是ngx_module_t类型的变量,其定义如下:

struct ngx_module_s {
    ngx_uint_t            ctx_index;
    ngx_uint_t            index;

    ngx_uint_t            spare0;
    ngx_uint_t            spare1;
    ngx_uint_t            spare2;
    ngx_uint_t            spare3;

    ngx_uint_t            version;

    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;

    ngx_int_t           (*init_master)(ngx_log_t *log);

    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);

    void                (*exit_master)(ngx_cycle_t *cycle);

    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

各成员变量含义如下

1. type

type变量是指模块的类型。Nginx中模块的类型定义如下:

#define NGX_CORE_MODULE      0x45524F43  /* "CORE" */
#define NGX_CONF_MODULE      0x464E4F43  /* "CONF" */
#define NGX_EVENT_MODULE     0x544E5645  /* "EVNT" */
#define NGX_HTTP_MODULE      0x50545448  /* "HTTP" */
#define NGX_MAIL_MODULE      0x4C49414D  /* "MAIL" */

也就是说,Nginx共有5中模块类型CORFCONFEVNTHTTPMAIL

2. index

index变量是指,该模块在ngx_modules数组中的次序,或者说下标。该变量在Nginx执行初始化的时候被初始化,初始化代码位于core\nginx.cmain函数中:

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

3. ctx_index

ctx_index是指,在ngx_modules数组中,该模块在相同类型的模块中的次序。

例如,ngx_modules数组中有多个类型为NGX_EVENT_MODULE的模块,那么其中一个模块的ctx_index初始化如下:

// 源码文件event\ngx_event.c
    ngx_event_max_module = 0;
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }
        ngx_modules[i]->ctx_index = ngx_event_max_module++;
    }

4. ctx

ctxvoid *指针型变量,这是指向与模块相关的上下文

这里先解释一下什么叫与模块相关的上下文

上下文这个概念一般可以理解为运行环境,那么与模块相关的上下文就是指模块运行环境。换句话说,就是一些参数的配置信息。因为配置一般意味着设置,而设置的对象就是模块的运行环境。比如,ngx_core_modules运行时的usergroup,就属于运行环境,同时,也属于配置项。

由于模块的参数一般比较多,正确设置参数的配置需要很多操作。所以,上下文其实也意味着一组操作,这组操作的目的在于根据配置,正确设置模块的参数信息。同时,这也意味着,不同的模块,这组操作是不同的,因此,这里需要将ctx设置为void *指针,来进行泛型的操作。

ngx_core_module为例,其ctx指向的上下文是这个样子的:

static ngx_core_module_t  ngx_core_module_ctx = {
    ngx_string("core"),
    ngx_core_module_create_conf,
    ngx_core_module_init_conf
};

其中ngx_core_module_t的定义如下:

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_init_conf函数中,初始化了一个ngx_core_conf_t的结构体。

因此,可知,void *ctx指向了一组操作,这组操作的目的在于初始化一个与模块配置信息相关的结构体
这里涉及到了三个结构体:

  • ngx_module_t ngx_core_module:用来表示模块本身,保存在ngx_modules数组中;
  • ngx_core_conf_t core_conf:用来保存对该模块的配置信息;
  • ngx_core_module_t ngx_core_module_ctx:用来初始化ngx_core_conf_t中的成员变量;

6. commands

commands是ngx_command_t数组,表示一组配置文件中的可配项(指令)。

例如,在配置文件nginx.confworker_processes 1;对应commands数组中的一项,该项的定义如下,类型为ngx_command_t

{ ngx_string("worker_processes"),
      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
      ngx_set_worker_processes,
      0,
      0,
      NULL }

其中ngx_command_t定义如下:

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

所以,commands的含义在于该模块的配置项在配置文件中的指令

ngx_module_t 总结

到这里,一个完整的模块所需的各个部分逐渐显露出来了:

  1. ngx_module_t结构体,用来表示模块的定义,被放入ngx_modules数组中。
  2. ngx_<module name>_conf_t结构体,用来表示模块的配置项,这个结构体随模块的不同而不同,通过上下文操作及commands中定义的指令,由配置文件确定其成员值。
  3. ngx_<module>_module_t结构体,表示一组用来初始化ngx_<module name>_conf_t结构体的操作。在Nginx初始化时被调用,初始化ngx_<module name>_conf_t结构体。
  4. ngx_command_t结构体数组,用来表示该模块可以在配置文件中配置的项目,及其操作指令。在Nginx初始化解析配置文件后,调用ngx_command_t中的函数set初始化ngx_<module name>_conf_t结构体。

总结

Nginx中,一个模块运行所需的各种配置参数被封装成了ngx_<module name>_conf_t结构体。

这个结构体内成员变量的初始化被分成了两部分:

  1. 模块相关的上下文ngx_<module>_module_t:即定义了一组初始化操作,用来将该结构体内的各成员初始化。
  2. 配置文件相关指令ngx_command_t:即规定了配置文件中可以使用的指令及其对应的操作函数。

而模块本身被封装成了ngx_module_t类型结构体,被用来供Nginx统一管理和使用。

模块和配置的联系非常紧密,因此,这里仅仅先大略的分析一下模块功能的整体框架,下一篇中,将在本篇的基础上分析Nginx的配置功能。


_Zhao
1.2k 声望82 粉丝