Nginx 是基于模块进行构建的,将核心功能和附加功能都通过模块化的方式实现,可以让每个模块保持相对简单,每个模块只负责一部分功能。Nginx 编译后是一个二进制模块,包含哪些模块需要在编译时进行确定。
Build from source
Nginx 在编译之前需要先使用源码包中提供的 auto/configure
工具,构建命令如代码 1-1 所示。这条命令会生成构建需要使用的 Makefile 和配置文件。在使用这个命令行文件时,不但需要指定一些 Nginx 运行时需要的文件路径,还需要指定需要包含的模块,这些模块都是可选的,Nginx 还有一些必须模块,这些模块不需要在这里指定。
./auto/configure \ ─╯
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--with-file-aio \
--with-threads \
--with-http_addition_module \
--with-http_auth_request_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_mp4_module \
--with-http_random_index_module \
--with-http_realip_module \
--with-http_secure_link_module \
--with-http_slice_module \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_sub_module \
--with-http_v2_module \
--with-mail \
--with-mail_ssl_module \
--with-stream \
--with-stream_realip_module \
--with-stream_ssl_module \
--with-stream_ssl_preread_module
代码 1-1:configure
命令执行结束后,会生成几个文件,其中ngx_modules.c
中定义了所有需要编译进来的 module,这里 module 是有顺序的,后面使用到的 module 可能会使用到前面 module 中准备好的某些变量。
ngx_module_t *ngx_modules[] = {
&ngx_core_module,
&ngx_errlog_module,
&ngx_conf_module,
&ngx_openssl_module,
&ngx_regex_module,
&ngx_events_module,
&ngx_event_core_module,
&ngx_epoll_module,
&ngx_thread_pool_module,
&ngx_http_module,
&ngx_http_core_module,
&ngx_http_log_module,
&ngx_http_upstream_module,
&ngx_http_v2_module,
&ngx_http_static_module,
// ...
&ngx_stream_upstream_zone_module,
&ngx_stream_ssl_preread_module,
NULL
};
Basic Structures
ngx_module_s 这个结构体包含了一个模块所需要的全部信息。结构体内最重要的就是 L17 ~ L26 的几个函数,这些函数会在特定的时间被调用,帮助完成模块的初始化和退出,对于大部分模块来说这些字段可能是 NULL
值。
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;
};
代码 2-1:ngx_module_s
这里需要注意的时 ctx 和 type 字段,ctx 并没有指定类型,它的类型实际上通过 type 指定,一些需要的特殊位置执行的代码就可以放到 ctx 中,然后根据 type 保证这一部分代码的执行。这里以 NGX_CORE_MODULE
这一类型为例,在函数 ngx_init_cycle
中,有如下一段代码,这里会遍历所有的 module,但是只会找到类型为 NGX_CORE_MODULE
类型的模块,然后执行 ctx 中的 create_conf
函数。
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;
}
}
代码 2-2:ngx_module_s.ctx & ngx_module_s.type
图 1: ngx_core_modules_t
Event Module
下面以 nginx_event_module 为例,探讨一下 nginx 是如何通过模块化的方式支持多种异步机制的。仔细观察生成的 ngx_modules
不难发现,与事件有关的模块有三个,分别是 ngx_events_module
, ngx_core_event_module
还有 ngx_epoll_module
,其中前两个是所有 Nginx 都要有的,而最后一个是配置脚本根据操作系统版本,还有支持的异步机制来确定的。
首先是 ngx_events_module,它的类型为 NGX_CORE_MODULE
,而且结构体内的七个函数都为 NULL
,所以这一模块的配置都保存在了 ngx_events_module_ctx
中,这个变量也只有一个 ngx_event_init_conf
需要执行。在 Connection Manager 中已经简单介绍过这个函数,它的主要作用就是将 ngx_listening_t
为每个进程复制一份。
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
};
代码 3-1:ngx_events_module
图 2:ngx_events_module_ctx
接下来的 ngx_event_core_module
,类型变成了 NGX_EVENT_MODULE
,init_module
和 init_process
两个字段也有值了。init_module
在创建 Worker 线程之前执行,在这个函数中会创建多个线程共享的全局变量。init_process
在创建完成的 Worker 中执行,作用是初始化一些本进程专用的变量。
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 */
ngx_event_process_init, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
代码 3-2:ngx_event_module_t
图 3:ngx_event_core_module
需要注意的是在 ngx_event_process_init
中,还会遍历所有的 NGX_EVENT_MODULE
类型的 module,调用 module.ctx.action.init
函数。
static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
// ...
for (m = 0; cycle->modules[m]; m++) {
if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
continue;
}
if (cycle->modules[m]->ctx_index != ecf->use) {
continue;
}
module = cycle->modules[m]->ctx;
if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
/* fatal */
exit(2);
}
break;
}
// ...
}