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_tngx_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;}// ...}
