目录
前言
前面在介绍ngx_cycle_t结构体的初始化时,我们深入到了ngx_cycle_init函数中。在那里我们说过,对ngx_cycle_t->conf_ctx的初始化是占篇幅最大的一部分。而之前我们在介绍ngx_cycle_t结构体中的各个成员时,曾经说过,conf_ctx保存的主要是Nginx中所有模块的配置结构体,当时说模块的配置结构体主要是用于保存它感兴趣的配置项的值,而对这些模块配置结构体的初始化过程是随着对配置文件的解析一起进行的,也就是在解析配置文件的过程中不断初始化这些配置结构体。当然并不是所有的配置结构体都是由感兴趣配置项组成的,有些模块的配置结构体主要是起到组织作用,除此之外,还会保存一些重要的属性信息,当然这些信息并不是从配置文件中得到的。这些我们在这篇文章中都会进行仔细分析。
下面我们先从conf_ctx说起,然后引出Nginx的模块类型,并根据Nginx模块结构体来介绍Nginx是怎么随着配置文件的解析不断加载它的配置结构信息的,这里其实就是介绍各个模块是怎么找到它的感兴趣配置项的。当然我们会顺便介绍一下Nginx配置文件的解析过程。
关于conf_ctx
在介绍ngx_cycle_t结构体时,我们曾经简单介绍过conf_ctx组织模块配置结构体的形式。如下图所示
所有数组里面的元素都是指针类型,因为不管是什么类型的指针,所占的内存容量都是一样的,因此将数组元素设置成指针类型有利于实现多态,也就是同一个数组里面的不同元素可以指向不同的结构体。
这里需要注意的是,虽然根据图中,不同level的数组具有的含义都不一样,但事实是:图中每个数组中的每个元素指向的都是一个模块的配置结构体。这句话中还包含了另一层含义,也就是可能有些模块的配置结构体时一个数组形式。之所以说是数组形式,是因为配置结构体实质是struct类型的。比如
typedef struct {
void **main_conf;
void **srv_conf;
void **loc_conf;
} ngx_http_conf_ctx_t;
这是某个核心模块的配置结构体的形式,虽然它是struct类型,但是它的形式是数组形式。之所以struct可以表现出数组的形式,主要的原因正如我们前面所说,利用到了指针的多态性质。这个数组包含三个元素。
虽然说不管是哪个level的数组中的元素指向的都是模块的配置结构体,但是从图中我们也可以看到,这里面是有层级关系的,这种层级关系表现在配置文件中就是嵌套的语法形式。因此我们可以说模块之间是存在一定的主从关系的。我们下面将分析Nginx中模块的形式,剖析模块的这种层级主从关系。完了再回头看conf_ctx。
Nginx中的模块
所谓 模块就是C中的一个struct。下面先从Nginx模块的形式开始,然后介绍Nginx的模块类型,最后回到conf_ctx看各种类型的模块是怎么在conf_ctx中组织起来的。
Nginx模块的形式(ngx_module_s)
Nginx模块就是一个ngx_module_s结构体,它具有以下形式
struct ngx_module_s {
ngx_uint_t ctx_index;
ngx_uint_t index;
char *name;
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t version;
const char *signature;
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;
};
我们这里主要解释其中的以下几个比较重要的成员变量:
- ctx_index
- index
- ctx
- commands
- type
ctx_index:
ctx_index记录的是该模块的配置结构体在数组数组中的下标。根据前面的图我们知道,每个模块的配置结构体都会被图中某个数组中的某个元素中保存的指针指向,ctx_index成员指向的就是这个数组元素在数组中的下标。
index
Nginx中有一个全局数组:
ngx_module_t *ngx_modules[];
Nginx所有的模块(ngx_module_s )都会保存在这个数组中,而index成员表示的就是模块在这个全局数组中的下标。
type:
type标识的是该模块的类型。在nginx中,一共有下面这些类型的模块:
- NGX_HTTP_MODULE
- NGX_CORE_MODULE
- NGX_MAIL_MODULE
- NGX_STREAM_MODULE
- NGX_EVENT_MODULE
其中NGX_CORE_MODULE表示这个模块是核心模块,它和其他几种类型的模块有根本区别。其他四种模块都是为了实现具体功能的模块,其实为了增加新的功能,我们可以继续添加更多的模块,不过目前nginx只有这四种功能的模块。
既然核心模块没有涉及具体的功能,那它存在的作用是什么呢?其实核心模块主要有两个作用:
- 将属于同一类功能的所有模块组织起来放在conf_ctx中。(用核心模块的配置结构体组织)
- 核心模块也会根据配置文件设置自己的配置结构体,主要是配置一些服务器运行参数,比如worker process等等。
以上两点其实就是 核心模块的配置结构体怎么使用的两种方法。要么用来存一些参数,要么用来组织其他模块。
对于第一个功能,我们回到上面的conf_ctx图。level1数组中的元素存储的就是指向核心模块配置结构体的指针。这个指针指向的配置结构体要么是一个普通的struct,要么就是一个类似数组的struct,比如这样:
struct aa{
void** a;
void** b;
void** c;
};
亦即struct中每个成员的类型一样。
ctx:
type标识的是模块的类型,模块之所以要区分类型,主要是因为不能模块有不同的作用,而这种不同的作用就是同ctx来实现的。可以看到ctx是void*类型,也就是说它可以指向任意类型的变量。我们将看到,不同的模块ctx指向的类型的差异。
ctx ---------------type
1. ngx_event_module_t —— NGX_EVENT_MODULE
2. ngx_core_module_t ——- NGX_CORE_MODULE
3. ngx_http_module_t ——- NGX_HTTP_MODULE
4. ngx_mail_module_t ——– NGX_MAIL_MODULE
5. ngx_stream_module_t ——- NGX_STREAM_MODULE
这里稍微讲一下,从各种类型的模块的ctx的具体形式我们可以看到,ctx指向的结构体中包含的主要是一些函数指针。这些函数的作用主要包括创建该模块对应的配置结构体,初始化配置结构体,以及根据配置文件设置配置结构体的工作完成后需要做的一些善后工作等等,都是在哎ctx中,具体的实现我们后面再说。
command
这个成员指向一个ngx_command_s类型的数组,看一下ngx_command_s的源码:
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;
};
这个成员的主要作用就是根据配置文件设置该模块的配置结构体中的一个配置项。所以我们可以简单地把每一个ngx_command_s 对应成一个配置项,name表示这个配置项字符形式,set将会将从配置文件中读到的配置项的值根据offset设置到该模块的配置结构体中的一个具体的成员上。
到这里基本就把模块这个结构体给解读清楚了。每个模块中除了以上一些数据成员之外,还会有一些函数指针,主要是一些初始化的函数,这些我们后面再说了。这里主要是需要关注什么时候会调用模块中的这些函数。
配置文件解析框架
这节并不会详细介绍配置文件的解析算法。我们只是为了了解一下配置文件是怎么被一步一步解析到模块的配置结构体中的,以及配置文件的每个配置项是怎么被设置到对应的配置结构体中的成员中的。
好了,回到ngx_cycle_init函数。这里是配置文件解析的入口。我们从这里开始进入配置文件解析的源代码分析。
首先是初始化level1数组,也就是创建核心模块模块的配置结构体:
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;
}
}
注意这里只是创建配置结构体,但是还没有对配置结构体中的成员进行设置初始化,这部分工作需要通过解析配置文件,调用每个模块中的command->set函数完成。这部分工作主要是由以下两个函数完成:
- ngx_conf_param
- ngx_conf_parse
其中ngx_conf_param是通过解析命令行参数来得到配置结构体感兴趣的配置项的值。ngx_conf_parse是通过解析配置文件得到配置项的值。两个函数的实现大同小异,只不过ngx_conf_parse需要读取配置文件,而ngx_conf_param不许要读取配置文件。下面我们 从ngx_conf_parse函数开始讲一下配置文件的解析过程。总得来说就是用自动状态机解析得到token,然后找对这个token感兴趣的模块。下面详细看一下代码的实现:
char *
ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
这个函数里面有一个死循环:
for ( ;; ) {
rc = ngx_conf_read_token(cf);
.....
}
这个死循环每次都会通过调用ngx_conf_read_token函数得到类似这样形式的一组数据:
配置项:配置值
并将配置项放在cf->name中,配置项胡值放在cf->args中。最后就会调用
ngx_conf_handler
将这个配置项的值设置到对它感兴趣胡模块胡配置结构体中,下面我们将会看到,对于一个给定的配置项,nginx是怎么找到对这个配置项感兴趣的模块的。
在拿到配置项和配置项对应的值之后,就会在ngx_conf_handler众遍历所有的模块,通过模块中的command成员找判断该模块是否对这个配置项感兴趣:
for (i = 0; cf->cycle->modules[i]; i++) {
cmd = cf->cycle->modules[i]->commands;
if (cmd == NULL) {
continue;
}
如果感兴趣胡话,就会调用command成员中的set函数:
rv = cmd->set(cf, cmd, conf);
对于command->set函数来说,它的参数conf就是指向该模块的配置结构体的指针。根据这个指针和command中对应该配置项在配置结构体中的offset,就可以在配置结构体中对这个配置项进行设置了。具体胡的set实现,我们后面再介绍模块的时候会详细说。
总结
这篇文章主要是介绍了模块的配置信息的加载。我们从ngx_cycle_t的conf_ctx结构说起,然后比较仔细地介绍了nginx中模块的概念,从C的角度说,模块就是一个struct,它会包含一些数据成员和函数指针,主要的用途是为了方便文件信息的加载。接下来我们介绍了一下配置文件的解析,配置文件的解析和模块中的command成员息息相关,我们做了一些简单的介绍,下一步在介绍具体的模块时,我们会更加深入。