起因

在《深入理解 Nginx 模块开发与架构解析》一书中提到在 Nginx 中除少量核心代码,其他一切皆为模块。核心模块(NGX_CORE_MODULE)是基础类型的模块,官方核心模块有六个具体模块:ngx_core_module、ngx_errlog_module、ngx_events_module、ngx_openssl_module、ngx_http_module、ngx_mail_module,非模块化的框架代码只需要关注于如何调用六个核心模块。书中并没有说明框架代码如何调用核心模块,以及如何通过核心模块调用具体代码,本文对此进行分析。

如何将模块组织进框架

在 configure 阶段会生成 ngx_modules.c 文件,其中有 ngx_modules 变量包含全部模块(有灵性的想法,不直接在代码中组织模块,通过脚本生成模块关系代码)。在 ngx_init_cycle (在其中调用 ngx_cycle_modules 初始化模块)函数中使用 ngx_cycle_modules 数组对 cycle 中的模块数组进行初始化。

框架代码如何调用核心模块以及核心模块到业务模块的转换

官方 Nginx 共有五大类型的模块:配置模块、核心模块、事件模块、HTTP 模块、mail 模块,其中配置模块与核心模块这两种类型由 Nginx 的框架代码定义并直接使用。但是事件模块、HTTP 模块、mail 模块不会与框架产生直接关系。事件模块、HTTP 模块、mail 模块在核心模块中各有一个模块作为自己的“代言人”,并在同类模块中有一个作为核心业务与管理功能的模块。

1. 概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
...... // 省略其他代码
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;
}
}
......
conf.ctx = cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;

if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
......
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}

module = cycle->modules[i]->ctx;

if (module->init_conf) {
if (module->init_conf(cycle,
cycle->conf_ctx[cycle->modules[i]->index])
== NGX_CONF_ERROR)
{
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
}
}
...... // 省略其他代码
}

观察上面的代码,可以看到会对所有的核心模块(NGX_CORE_MODULE 类型)调用 create_confngx_conf_parseinit_conf,框架直接调用核心模块并没有调用业务模块。核心模块中 ngx_mail_modulengx_http_modulengx_openssl_modulengx_events_modulengx_error_module 模块作为其他模块在核心模块的代理,当框架调用到他们时会在其 commands 数组中指定处理指令,而在其处理指令中会将待处理的指令类型、指令模块修改为具体类型,这是从核心模块到具体业务模块处理的转化,例如如下 ngx_http_module 模块的 ngx_http_block 处理代码:

1
2
3
4
5
6
7
8
9
10
static char *
ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
... // 忽略其他代码
cf->module_type = NGX_HTTP_MODULE;
cf->cmd_type = NGX_HTTP_MAIN_CONF;
rv = ngx_conf_parse(cf, NULL);
... // 忽略其他代码
}

2. 框架如何调用事件模块代理 “ngx_events_module”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
};


static ngx_core_module_t ngx_events_module_ctx = {
ngx_string("events"),
NULL,
ngx_event_init_conf
};


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

从上面代码中可以看到 ngx_events_module 的具体化接口 ngx_events_module_ctx 并不存在 create_conf 调用,而 ngx_event_init_conf 也仅做了 ngx_events_module 模块存在判断,并未做其他工作。那我们猜测 ngx_events_block 做了事件模块的代理工作,下面分析 ngx_events_block 代码。

1
2
3
4
5
6
7
8
pcf = *cf;
cf->ctx = ctx;
// 指定处理事件类型模块的指令
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;

rv = ngx_conf_parse(cf, NULL);

ngx_events_block 中会遍历 cycle 中所有 NGX_EVENT_MODULE 模块(注意 ngx_events_module 为核心模块类型,此时为核心模块到事件模块的处理转换,参见上面的代码),并调用模块的 create_conf 接口创建存储配置项的机构体指针。接下来会为所有的事件模块解析配置文件,调用 ngx_conf_parse 解析 NGX_EVENT_MODULE 类型模块感兴趣的配置项。之后对所有的 NGX_EVENT_MODULE 类型模块回调 init_conf

ngx_event_core_module 模块作为 NGX_EVENT_MODULE 类型模块的一员,同样会经历上面的步骤:create_confinit_conf 接口回调以及指令解析。ngx_event_core_module 定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
static ngx_str_t  event_core_name = ngx_string("event_core");


static ngx_command_t ngx_event_core_commands[] = {

{ ngx_string("worker_connections"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_connections,
0,
0,
NULL },

{ ngx_string("use"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_use,
0,
0,
NULL },

{ ngx_string("multi_accept"),
...},

{ ngx_string("accept_mutex"),
...},

{ ngx_string("accept_mutex_delay"),
...},
{ ngx_string("debug_connection"),
...},

ngx_null_command
};


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


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 */ // core 模块作为代理模块就可以做事件模块的模块初始化工作
ngx_event_process_init, /* init process */ // core 模块作为代理模块就可以做事件模块的进程初始化工作
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};

观察 ngx_event_core_module 就可以看到 module_initprocess_init 是切人点,实现事件模块的代理。

3. 总结

与事件模块类似,HTTP 模块、mail 模块也是通过核心模块转换到核心模块的具象化类型进行处理。这样做的好处是,框架关心核心模块,核心模块中有某个具象模块的代理用来从核心模块到具象模块的转变,将复杂的文件分解抽象以模块的方式组织代码。在之后的源码阅读中可以从其核心模块代理开始,逐步转换到业务模块管理者,再跳转到具体的业务模块实现。