目录
前言
前面梗概介绍了Nginx模块配置的信息。简单地说,nginx的灵活性主要归功于他的高度模块化和可配置性。其中可配置性依赖于配置文件。在nginx中,模块就是一个struct类型的全局变量。Nginx通过为每个模块维护一个配置结构体来使得每个模块的行为根据配置文件而改变。
Nginx所有模块的配置结构体都有组织地放在nginx_cycle的conf_ctx中。conf_ctx维护的是一个多维数据,逻辑上可以简单地归结为下图的形式:
其中level1数组中的每个元素存储的是指向 核心模块的配置结构体的指针。核心模块的作用是 管理相同类型的功能模块。
这篇博文主要是讲一下event模块的解析配置过程。后面会依次介绍http,stream等功能模块的配置组织过程。其实都大同小异。
核心模块
event的核心模块是:
//nginx_event.c
ngx_module_t ngx_events_module = {
NGX_MODULE_V1,
&ngx_events_module_ctx, /* module context */
ngx_events_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
注意,核心模块的的type=NGX_CORE_MODULE。
对于核心模块的来说,它的配置流程主要是包括以下几步:
1. 如果核心模块的ctx,也就是ngx_core_module_t的create_conf函数指针不为空,则调用这个函数,创建该核心模块的配置结构体。
2. 调用ngx_conf_parse和ngx_conf_param函数解析核心模块组织下的所有相同类型的功能模块。核心模块的结构体配置主要是在这部分完成。
3. 如果核心模块的ctx,也就是ngx_core_module_t的init_conf函数指针不为空,则调用这个函数。
以上这些步骤都是可以从ngx_init_cycle函数中清晰看到的。
显然这里的核心就是第二部分了。
对于event模块来说,所有event模块的感兴趣配置项都在配置文件的event{}区块中,如下所示:
events{
......
}
而”events”配置项的感兴趣模块是核心模块
ngx_events_module
我们从它的command成员可以看到:
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
};
当遇到events配置项时,核心模块ngx_events_module会调用ngx_events_block函数:
static char *
ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
这个函数就是解析event类型模块的入口,这里我们就不贴代码了。从ngx_events_block的源码可以看到,这个函数主要是完成以下几个工作:
1. 调用各个event类型模块的create_conf创建各自的配置结构体,并将配置结构体的指针设置到指针数组中。
2. 调用ngx_conf_parse函数解析配置文件的events{}区块中的各个配置项。这是设置各个event模块的配置结构体的关键
3. 调用各个event模块的init_conf函数
可以看到,这里event模块的配置流程和之前核心模块的配置流程基本都是一样的。当然,这里最重要的也是第二项工作:ngx_conf_parse
第二项工作其实可以简单地看成是:依次遍历events{}块中的所有配置项,每遇到一个配置项,就遍历一次Nginx中所有的event配置模块的command,看是否有event对这个配置项感兴趣,如果感兴趣,则调用command中的set根据配置项的值设置这个模块的配置结构体或者做一些其他的工作。
因此可以看出,event模块中的command成员包含了该模块的感兴趣配置项,以及如果配置文件中出现这个配置项时该模块的执行动作、下面我们依次介绍一下各个event模块,和它的command成员。
几种event模块
event模块分两种:
1. ngx_event_core_module
2. ngx_aio_module
ngx_devpoll_module
ngx_epoll_module
ngx_kqueue_module
ngx_poll_module
.....
第二种是具体的功能模块,每个nginx运行实例只能使用一个模块。第二种是用来保存一些事件模块相关的全局信息的组织性质的模块。我们先从第一种模块介绍开始:
ngx_event_core_module
ngx_module_t ngx_event_core_module = {
NGX_MODULE_V1,
&ngx_event_core_module_ctx, /* module context */
ngx_event_core_commands, /* module directives */
NGX_EVENT_MODULE, /* module type */
NULL, /* init master */
ngx_event_module_init, /* init module */
ngx_event_process_init, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
其中他的ctx是:
ngx_event_module_t ngx_event_core_module_ctx = {
&event_core_name,
ngx_event_core_create_conf, /* create configuration */
ngx_event_core_init_conf, /* init configuration */
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
};
前面我们讲过,核心模块在开始真正解析event模块时,会先调用event模块的create_conf函数,这里就是ngx_event_core_create_conf函数:
static void *
ngx_event_core_create_conf(ngx_cycle_t *cycle)
{
ngx_event_conf_t *ecf;
ecf = ngx_palloc(cycle->pool, sizeof(ngx_event_conf_t));
if (ecf == NULL) {
return NULL;
}
ecf->connections = NGX_CONF_UNSET_UINT;
ecf->use = NGX_CONF_UNSET_UINT;
ecf->multi_accept = NGX_CONF_UNSET;
ecf->accept_mutex = NGX_CONF_UNSET;
ecf->accept_mutex_delay = NGX_CONF_UNSET_MSEC;
ecf->name = (void *) NGX_CONF_UNSET;
#if (NGX_DEBUG)
if (ngx_array_init(&ecf->debug_connection, cycle->pool, 4,
sizeof(ngx_cidr_t)) == NGX_ERROR)
{
return NULL;
}
#endif
return ecf;
}
这个函数会创建该模块的配置结构体:ngx_event_conf_t *ecf;
并对ecf中的一些项进行初始化,我们这里就不详细介绍这里的各个项的具体含义了。下面我们看一下ngx_event_core_module 的command,也就是它的感兴趣配置项,从commands成员中我们可以看到,它主要对以下这些配置项感兴趣:
- worker_connections
- connections
- use
- multi_accept
- accept_mutex
- accept_mutex_delay
- debug_connection
从各个command的set函数我们可以看到对这些配置项的配置过程。
比如对于connections配置项的set函数
static char *
ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
从这个函数的实现我们可以发现,connections配置项主要是设置Nginx连接池的连接总数量的。
还有就是我们看一下”use”配置项,这个配置项主要是配置Nginx实例使用的事件驱动类型,比如如果用的是epoll,则配置文件中会有如下形式的配置:
use epoll;
这个配置项会设置配置结构体中的use和name两项。这些可以从use配置项的set函数看出来:
static char *
ngx_event_use(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
.....
ecf->use = ngx_modules[m]->ctx_index;
ecf->name = module->name->data;
.....
}
这样,我们就可以通过ngx_event_core_module 的配置结构体找到Nginx使用的事件驱动。这就将事件模型嵌入到了nginx中。
同样,ngx_event_core_module配置结构体的其他成员我们也可以通过这个形式分析得到,这里就不讲了。
正如前面所说,配置一个event模块主要分为三个工作:
1. create_conf
2. 解析配置文件
3. init_conf
现在还剩init_conf函数没有调用,ngx_event_core_module 的init_conf是:
ngx_event_core_init_conf
这个函数主要作用也是设置配置结构体。它主要是为了弥补第二项工作,因为可能配置文件中没有出现某些该模块感兴趣的配置项,这就使得该模块的配置结构体中部分成员得不到初始化,init_conf函数的作用就是初始化这些没有在配置文件中设置的项。
epoll和kqueue等功能性的event模块
剩下的这些就比较简单了。event模块的ctx形式如下所示:
typedef struct {
ngx_str_t *name;
void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
ngx_event_actions_t actions;
} ngx_event_module_t;
其中actions是一系列的添加事件,删除事件等函数指针。前面ngx_event_core_module的ctx中actions中所有函数指针都是NULL,是因为ngx_event_core_module 不负责具体的事件功能。现在讲的事件模块是负责具体事件模块功能实现的,因此actions不为NULL。而ngx_event_core_module相当于将所有事件功能模块组织起来,通过ngx_event_core_module 可以找到当前Nginx实例的配置文件所使用的具体event功能模块:epoll,poll,select或其他。
actions中的各个函数指针是事件功能模块的核心,当然,这些函数的执行可能会受到一些配置项的影响,这些配置项的值就存储在对应模块的配置结构体中。因此对具体功能的事件模块,他的配置解析过程就是解析,设置它感兴趣的配置项,而且各个实现具体功能的事件模块都是相对独立的,不像ngx_event_core_module 那样,会和其他事件模块有关系。因此具体功能的事件模块还是很简单的。
嵌入到Nginx框架中
前面讲的所有工作都是模块自身的一些设置。但是模块要发挥作用,肯定是要嵌入到Nginx框架中的。其实,模块的配置结构已经通过cycle->conf_ctx嵌入到了nginx框架中,也就是说Nginx框架可以通过cycle->conf_ctx找到所有模块的配置结构体。
对于event模块来说,为了方便事件模块协调Nginx工作,nginx将具体功能的事件模块放到一个全局变量中:
ngx_event_actions_t ngx_event_actions;
通过这个全局变量就可以找到事件模块实现的所有事件操作函数。
这些设置是Nginx通过调用ngx_event_core_module模块的init_process函数在初始化进程时完成的。
同时在这个init_process函数中,还会完成其他一些初始化与事件模块相关的操作,包括连接池初始化
cycle->connections =
ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
事件池初始化:
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
cycle->log);
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
cycle->log);
等等。
总之与事件相关的初始化操作基本都是在init_process中完成的。
总结
这篇博文介绍了一下event模块,包括对应的核心模块,ngx_event_core_module事件模块,以及具体实现功能的事件模块。其中ngx_event_core_module是我们介绍的重点。
核心模块感兴趣的配置项是events。他是解析配置文件中
events{
...
}
块的入口。
然后是ngx_event_core_module事件模块,这个模块的感兴趣配置项主要是一些与具体功能的事件模块和全局事件功能有关的变量,比如连接池。简单地说,ngx_event_core_module是管理其他具体功能的事件模块。Nginx实例通过ngx_event_core_module可以找到它使用的是哪个事件模块来实现事件驱动。这样在实现具体功能的事件模块时可以相对独立。模块化更强。
最后我们介绍了怎么将模块嵌入到Nginx运行框架中。对于像Nginx这样高度模块化的程序来说,它有一个大的框架,然后在框架的各个涉及具体功能的部分留出一些接口,这些接口的具体功能通过嵌入模块来实现。整个过程有点像拼图,各个模块都相对独立,我们可以通过实现自己的模块来改变Nginx的运行行为。实现高度模块化,极大提高了程序的可维护性和可拓展性。