Nginx 是基于模块进行构建的,将核心功能和附加功能都通过模块化的方式实现,可以让每个模块保持相对简单,每个模块只负责一部分功能。Nginx 编译后是一个二进制模块,包含哪些模块需要在编译时进行确定。

Build from source

Nginx 在编译之前需要先使用源码包中提供的 auto/configure 工具,构建命令如代码 1-1 所示。这条命令会生成构建需要使用的 Makefile 和配置文件。在使用这个命令行文件时,不但需要指定一些 Nginx 运行时需要的文件路径,还需要指定需要包含的模块,这些模块都是可选的,Nginx 还有一些必须模块,这些模块不需要在这里指定。

  1. ./auto/configure \ ─╯
  2. --prefix=/etc/nginx \
  3. --sbin-path=/usr/sbin/nginx \
  4. --conf-path=/etc/nginx/nginx.conf \
  5. --error-log-path=/var/log/nginx/error.log \
  6. --http-log-path=/var/log/nginx/access.log \
  7. --pid-path=/var/run/nginx.pid \
  8. --lock-path=/var/run/nginx.lock \
  9. --http-client-body-temp-path=/var/cache/nginx/client_temp \
  10. --http-proxy-temp-path=/var/cache/nginx/proxy_temp \
  11. --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
  12. --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
  13. --http-scgi-temp-path=/var/cache/nginx/scgi_temp \
  14. --with-file-aio \
  15. --with-threads \
  16. --with-http_addition_module \
  17. --with-http_auth_request_module \
  18. --with-http_dav_module \
  19. --with-http_flv_module \
  20. --with-http_gunzip_module \
  21. --with-http_gzip_static_module \
  22. --with-http_mp4_module \
  23. --with-http_random_index_module \
  24. --with-http_realip_module \
  25. --with-http_secure_link_module \
  26. --with-http_slice_module \
  27. --with-http_ssl_module \
  28. --with-http_stub_status_module \
  29. --with-http_sub_module \
  30. --with-http_v2_module \
  31. --with-mail \
  32. --with-mail_ssl_module \
  33. --with-stream \
  34. --with-stream_realip_module \
  35. --with-stream_ssl_module \
  36. --with-stream_ssl_preread_module

代码 1-1:configure

命令执行结束后,会生成几个文件,其中ngx_modules.c 中定义了所有需要编译进来的 module,这里 module 是有顺序的,后面使用到的 module 可能会使用到前面 module 中准备好的某些变量。

  1. ngx_module_t *ngx_modules[] = {
  2. &ngx_core_module,
  3. &ngx_errlog_module,
  4. &ngx_conf_module,
  5. &ngx_openssl_module,
  6. &ngx_regex_module,
  7. &ngx_events_module,
  8. &ngx_event_core_module,
  9. &ngx_epoll_module,
  10. &ngx_thread_pool_module,
  11. &ngx_http_module,
  12. &ngx_http_core_module,
  13. &ngx_http_log_module,
  14. &ngx_http_upstream_module,
  15. &ngx_http_v2_module,
  16. &ngx_http_static_module,
  17. // ...
  18. &ngx_stream_upstream_zone_module,
  19. &ngx_stream_ssl_preread_module,
  20. NULL
  21. };

代码 1-2:ngx_modules

Basic Structures

ngx_module_s 这个结构体包含了一个模块所需要的全部信息。结构体内最重要的就是 L17 ~ L26 的几个函数,这些函数会在特定的时间被调用,帮助完成模块的初始化和退出,对于大部分模块来说这些字段可能是 NULL 值。

  1. struct ngx_module_s {
  2. ngx_uint_t ctx_index;
  3. ngx_uint_t index;
  4. char *name;
  5. ngx_uint_t spare0;
  6. ngx_uint_t spare1;
  7. ngx_uint_t version;
  8. const char *signature;
  9. void *ctx;
  10. ngx_command_t *commands;
  11. ngx_uint_t type;
  12. ngx_int_t (*init_master)(ngx_log_t *log);
  13. ngx_int_t (*init_module)(ngx_cycle_t *cycle);
  14. ngx_int_t (*init_process)(ngx_cycle_t *cycle);
  15. ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
  16. void (*exit_thread)(ngx_cycle_t *cycle);
  17. void (*exit_process)(ngx_cycle_t *cycle);
  18. void (*exit_master)(ngx_cycle_t *cycle);
  19. uintptr_t spare_hook0;
  20. uintptr_t spare_hook1;
  21. uintptr_t spare_hook2;
  22. uintptr_t spare_hook3;
  23. uintptr_t spare_hook4;
  24. uintptr_t spare_hook5;
  25. uintptr_t spare_hook6;
  26. uintptr_t spare_hook7;
  27. };

代码 2-1:ngx_module_s

这里需要注意的时 ctx 和 type 字段,ctx 并没有指定类型,它的类型实际上通过 type 指定,一些需要的特殊位置执行的代码就可以放到 ctx 中,然后根据 type 保证这一部分代码的执行。这里以 NGX_CORE_MODULE 这一类型为例,在函数 ngx_init_cycle 中,有如下一段代码,这里会遍历所有的 module,但是只会找到类型为 NGX_CORE_MODULE 类型的模块,然后执行 ctx 中的 create_conf 函数。

  1. for (i = 0; cycle->modules[i]; i++) {
  2. if (cycle->modules[i]->type != NGX_CORE_MODULE) {
  3. continue;
  4. }
  5. module = cycle->modules[i]->ctx;
  6. if (module->create_conf) {
  7. rv = module->create_conf(cycle);
  8. if (rv == NULL) {
  9. ngx_destroy_pool(pool);
  10. return NULL;
  11. }
  12. cycle->conf_ctx[cycle->modules[i]->index] = rv;
  13. }
  14. }

代码 2-2:ngx_module_s.ctx & ngx_module_s.type

ngx_modules-core_module.drawio.svg
图 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 为每个进程复制一份。

  1. static ngx_core_module_t ngx_events_module_ctx = {
  2. ngx_string("events"),
  3. NULL,
  4. ngx_event_init_conf
  5. };
  6. ngx_module_t ngx_events_module = {
  7. NGX_MODULE_V1,
  8. &ngx_events_module_ctx, /* module context */
  9. ngx_events_commands, /* module directives */
  10. NGX_CORE_MODULE, /* module type */
  11. NULL, /* init master */
  12. NULL, /* init module */
  13. NULL, /* init process */
  14. NULL, /* init thread */
  15. NULL, /* exit thread */
  16. NULL, /* exit process */
  17. NULL, /* exit master */
  18. NGX_MODULE_V1_PADDING
  19. };

代码 3-1:ngx_events_module

ngx_modules-ngx_events_module.drawio.svg
图 2:ngx_events_module_ctx

接下来的 ngx_event_core_module ,类型变成了 NGX_EVENT_MODULEinit_moduleinit_process 两个字段也有值了。init_module 在创建 Worker 线程之前执行,在这个函数中会创建多个线程共享的全局变量。init_process 在创建完成的 Worker 中执行,作用是初始化一些本进程专用的变量。

  1. static ngx_event_module_t ngx_event_core_module_ctx = {
  2. &event_core_name,
  3. ngx_event_core_create_conf, /* create configuration */
  4. ngx_event_core_init_conf, /* init configuration */
  5. { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
  6. };
  7. ngx_module_t ngx_event_core_module = {
  8. NGX_MODULE_V1,
  9. &ngx_event_core_module_ctx, /* module context */
  10. ngx_event_core_commands, /* module directives */
  11. NGX_EVENT_MODULE, /* module type */
  12. NULL, /* init master */
  13. ngx_event_module_init, /* init module */
  14. ngx_event_process_init, /* init process */
  15. NULL, /* init thread */
  16. NULL, /* exit thread */
  17. NULL, /* exit process */
  18. NULL, /* exit master */
  19. NGX_MODULE_V1_PADDING
  20. };

代码 3-2:ngx_event_module_t

ngx_modules-ngx_event_core_module.drawio.svg
图 3:ngx_event_core_module

需要注意的是在 ngx_event_process_init 中,还会遍历所有的 NGX_EVENT_MODULE 类型的 module,调用 module.ctx.action.init 函数。

  1. static ngx_int_t
  2. ngx_event_process_init(ngx_cycle_t *cycle)
  3. {
  4. // ...
  5. for (m = 0; cycle->modules[m]; m++) {
  6. if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
  7. continue;
  8. }
  9. if (cycle->modules[m]->ctx_index != ecf->use) {
  10. continue;
  11. }
  12. module = cycle->modules[m]->ctx;
  13. if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
  14. /* fatal */
  15. exit(2);
  16. }
  17. break;
  18. }
  19. // ...
  20. }