Nginx epoll 事件驱动

本文基于 Nginx1.20.0

1. Nginx 模块

1.1 模块类型

高并发是 Nginx 最大的优势之一,而高并发的原因就是 Nginx 强大的事件模块。本文将重点介绍 Nginx 是如果利用 Linux 系统的 Epoll 来完成高并发的。

Nginx 框架定义了 6 种类型的模块,分别是 coreconfeventstreamhttpmail,所有的 Nginx 模块都必须属于这 6 类模块。

ngx_module_ttype 字段表示模块的类型,取值必须是以下 6 个宏:

#define NGX_CORE_MODULE      0x45524F43  // "CORE"  
#define NGX_CONF_MODULE      0x464E4F43  // "CONF"  
#define NGX_EVENT_MODULE     0x544E5645  // "EVNT"  
#define NGX_STREAM_MODULE    0x4D525453  // "STRM"  
#define NGX_HTTP_MODULE      0x50545448  // "HTTP"  
#define NGX_MAIL_MODULE      0x4C49414D  // "MAIL"  
  • Core 模块NGX_CORE_MODULE):目前 Nginx 有 10 个 Core 模块,如下:
    • **ngx_core_module**
    • ngx_regex_module
    • **ngx_events_module**
    • ngx_http_module
    • ngx_stream_module
    • ngx_thread_pool_module
    • ngx_errlog_module
    • ngx_openssl_module
    • ngx_google_perftools_module
    • ngx_mail_module
  • Event 模块NGX_EVENT_MODULE)目前 Nginx 有 11 个 Event 模块,如下:
    • **ngx_event_core_module**
    • **ngx_epoll_module**(重点关注)
    • ngx_poll_module
    • ngx_select_module
    • ngx_kqueue_module
    • ngx_eventport_module
    • ngx_devpoll_module
    • ngx_aio_module
    • ngx_rtsig_module
    • ngx_poll_module(win32)
    • ngx_select_module(win32)

其它类型模块与本文内容无关,就不列出来了。

对于每一个模块,都有一个 ngx_module_t 类型的结构体表示,结构体的 type 字段表明了模块的类型。

由于本文主要讲解 Linux 系统中使用 Epoll 来完成 Nginx 的事件驱动,故主要介绍 Core 模块的 **ngx_events_module** 与 Event 模块的 **ngx_event_core_module****ngx_epoll_module**

1.2 ngx_modules.c

Nginx 源码根目录中的 configure 脚本会生成源码文件 objs/ngx_modules.c,在这里所有的 Nginx 静态模块都被放进了一个普通的数组,用于启动时的初始化,例如:

// 定义在 objs/ngx_modules.c,是由 configure 脚本动态生成的
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_stream_upstream_zone_module,
    NULL
};

ngx_modules 数组简单地把所有 Nginx 模块线性存储在一起。注意,数组只是保存了模块的指针,模块的定义还是在各自实现文件里,所有模块的声明一定不能使用 static,否则 ngx_modules 会无法找到模块,导致编译错误。

另外一个数组 ngx_module_names 保存了所有模块的名称,它与 ngx_modules 是一一对应的。

// 定义在objs/ngx_modules.c(由configure脚本动态生成)
char *ngx_module_names[] = {	// 模块名称数组(与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_stream_upstream_zone_module",
    NULL
}

动态模块这里不讨论!

1.3 ngx_module_t

struct ngx_module_s {
    // 下面的几个成员通常使用宏NGX_MODULE_V1填充

    // 每类(http/event)模块各自的index,初始化为-1
    // ctx_index的初始化:以http模块为例,
    // 在ngx_http_module(它是core模块而不是http模块)里,当ngx_http_block()函数
    // 解析配置文件http块时,nginx会调用ngx_count_modules()函数,初始化http模块ctx_index成员
    ngx_uint_t            ctx_index;

    // 在ngx_modules数组里的唯一索引,main()里赋值
    // 使用计数器变量ngx_max_module
    ngx_uint_t            index;

    // 1.10,模块的名字,标识字符串,默认是空指针
    // 由脚本生成ngx_module_names数组,然后在ngx_preinit_modules里填充
    // 动态模块在ngx_load_module里设置名字
    char                 *name;

    // 两个保留字段,1.9之前有4个
    ngx_uint_t            spare0;
    ngx_uint_t            spare1;

    // nginx.h:#define nginx_version      1010000
    ngx_uint_t            version;

    // 模块的二进制兼容性签名,即NGX_MODULE_SIGNATURE
    const char           *signature;

    // 模块不同含义不同,通常是函数指针表,是在配置解析的某个阶段调用的函数
    // core模块的ctx
    //typedef struct {
    //    ngx_str_t             name;
    //    void               *(*create_conf)(ngx_cycle_t *cycle);
    //    char               *(*init_conf)(ngx_cycle_t *cycle, void *conf);
    //} ngx_core_module_t;
    void                 *ctx;

    // 模块支持的指令,数组形式,最后用空对象表示结束
    ngx_command_t        *commands;

    // 模块的类型标识,相当于RTTI,如CORE/HTTP/STRM/MAIL等
    ngx_uint_t            type;

    // 以下7个函数会在进程的启动或结束阶段被调用

    // init_master目前nginx不会调用
    ngx_int_t           (*init_master)(ngx_log_t *log);

    // 在ngx_init_cycle里被调用
    // 在master进程里,fork出worker子进程之前
    // 做一些基本的初始化工作,数据会被子进程复制
    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);

    // 在ngx_single_process_cycle/ngx_worker_process_init里调用
    // 在worker进程进入事件循环之前被调用,初始化每个子进程自己专用的数据
    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);

    // init_thread目前nginx不会调用
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);

    // exit_thread目前nginx不会调用
    void                (*exit_thread)(ngx_cycle_t *cycle);

    // 在ngx_worker_process_exit调用
    void                (*exit_process)(ngx_cycle_t *cycle);

    // 在ngx_master_process_exit(os/unix/ngx_process_cycle.c)里调用
    void                (*exit_master)(ngx_cycle_t *cycle);

    // 下面8个成员通常用用NGX_MODULE_V1_PADDING填充
    // 暂时无任何用处
    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. 事件模块初始化

众所周知,Nginx 是 Master/Worker 框架,在 Nginx 启动时是一个进程,在启动的过程中 Master 会 fork(2) 出了多个子进程作为 Worker(数量由 worker_processes 配置决定)。

主进程(Master)单实例运行,负责管理 Worker 进程,不直接处理客户端请求。而工作进程(Worker),多实例运行,实际处理请求(如 HTTP 连接、反向代理等)。

因此,事件模块的初始化也是分成两部分。一部分发生在 fork(2) 之前,主要是 Master 进程解析配置文件等操作,另外一部分发生在 fork(2) 之后,主要是 Worker 进程向 Epoll 中添加监听事件。

2.1 Master 进程对事件模块的初始化

启动进程对事件模块的初始化分为配置文件解析、监听端口和 ngx_event_core_module 模块的初始化。这三个步骤均在 ngx_init_cycle 函数进行。

调用关系:

main()
    |-> ngx_init_cycle()

ngx_init_cycle 函数的流程:

  1. 创建 cycle->conf_ctx 数组;
  2. 拷贝全局数组 ngx_modulescycle->modules
  3. 调用所有 core 模块的 create_conf 函数指针,创建出配置结构,填入数组;
  4. 设置 ngx_conf_t 的各个字段,作为配置解析的起始ctx
  5. 执行 ngx_conf_parse(),在模块数组里查找配置指令对应的解析函数并处理;
  6. 反复执行步骤5,直至整个配置文件处理完毕;
  7. 调用 core 模块 init_conf 函数指针,初始化 core 模块的配置。

红框是本节将要介绍的三部分内容。

2.1.1 Core 模块:ngx_events_module

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

static ngx_core_module_t  ngx_events_module_ctx = {
    ngx_string("events"),
    NULL,						// create_conf

    // 1.15.2之前只检查是否创建了配置结构体,无其他操作
    // 因为event模块只有一个events指令
    // 1.15.2之后增加新的代码
    ngx_event_init_conf			// init_conf
};

// events模块仅支持一个指令,即events块
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
};

2.1.2 解析配置文件

ngx_cycle_t

ngx_cycle_t 不仅存储了所有的模块信息、监听端口,也保存了所有的配置解析的其它相关信息。完全可以将其看成配置文件在内存中的表示。

// 定义在 core/ngx_cycle.h
struct ngx_cycle_s {
    void                  ****conf_ctx;
    ...
    ngx_module_t            **modules;
    ...
    ngx_array_t               listening;			// 监听的端口数组
    ...
}

conf_ctx 是 Nginx 存储配置数据的起点,但它的声明使用了连续四个星号,乍一看很难立即理解其含义。但实际上 conf_ctx 是一个二维数组,数组里的元素是 void* 指针,每一个指针又指向了另一个存储 void* 的数组(实际对应到 event 模块的配置存储)。

配置文件解析由 main() -> ngx_init_cycle() 函数完成,但 ngx_init_cycle() 在实际开始解析配置文件之前,会先给 cycle->conf_ctx 数组分配内存,调用所有 Core 模块的 create_conf 方法(如果实现了的话)。

ngx_conf_t

ngx_conf_t 结构定义在 core/ngx_config_file.h,是 Nginx 解析配置文件时的重要数据结构,表示解析当前配置指令时的运行环境数据(Context):

// 定义在core/ngx_core.h  
typedef struct ngx_conf_s ngx_conf_t;  

// 定义在core/ngx_config_file.h  
struct ngx_conf_s {
    ngx_array_t      *args;       	// 存储指令字符串数组(如["server", "127.0.0.1"])  
    ngx_pool_t       *pool;      	// 内存池对象  
    ngx_log_t        *log;        	// 日志对象  
    void             *ctx;        	// 当前环境(如ngx_http_conf_ctx_t)  
    ngx_uint_t        cmd_type;   	// 当前命令类型  
    ...
    ngx_conf_file_t	*conf_file;		// 当前的配置文件
    ...  // 其他数据成员  
};  

因为 Nginx 的配置文件是有层次的,分成了 main / http / server / location 等作用域,不同的域里指令的含义和处理方式都有可能不同(例如 http{} 块里的 server 指令和 upstream{} 块里的 server 指令就是完全不同的),所以 Nginx 在解析配置文件时必须使用 ngx_conf_t 来保存当前的基本信息,进入/退出一个配置块都会变更 ngx_conf_t,指令的解析必须要参考 ngx_conf_t 环境数据才能正确处理。

args 参数是我们最常用到的成员,它是一个 ngx_array_t,以 ngx_str_t 形式存储分割好的配置文件指令字符串。如果有 NgxStrArray arr(cf->args),那么 arr[0] 就是指令名,arr[1] 就是第一个参数,以此类推。

为 cycle->conf_ctx 数组分配内存

_ngx_cycle.c_ngx_init_cycle() 函数里创建 conf_ctx 数组的代码是:

cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void*));  

这实际上就是一个存储 void* 指针的普通数组,长度为 ngx_max_module,可以用来存放所有模块的配置数据结构(但实际上 Nginx 并没有这么做)。

Nginx 通过这种方式灵活地支持了不同模块的数据存储需求:对于 Core 模块,指针直接指向配置数据;而 event / stream / http 模块则指针指向的可以是另外一个数组,形成了一个树形结构

创建 Core 模块的配置结构

调用 Core 模块的 create_conf 函数指针创建 Core 模块的配置结构。

// 初始化core模块
for (i = 0; cycle->modules[i]; i++) {
    // 检查type,只处理core模块,数量很少
    if (cycle->modules[i]->type != NGX_CORE_MODULE) {
        continue;
    }

    //获取core模块的函数表
    module = cycle->modules[i]->ctx;

    // 创建core模块的配置结构体
    // 有的core模块可能没有这个函数,所以做一个空指针检查
    if (module->create_conf) {
        rv = module->create_conf(cycle);
        if (rv == NULL) {
            ngx_destroy_pool(pool);
            return NULL;
        }
        // 存储到cycle的配置数组里,用的是index,不是ctx_index
        cycle->conf_ctx[cycle->modules[i]->index] = rv;
    }
}

ngx_events_module 模块没有实现 create_conf 函数指针,但是实现了 init_conf,即 ngx_event_init_conf

因此,ngx_events_module 这个 Core 模块无法调用 create_conf 创建 ngx_events_module 模块的配置结构,并将其指针填充到 cycle->conf_ctx 数组中。只有等到解析配置文件时,遇到 events 块指令,才由 events 块指令处理函数 ngx_events_block() 来创建该配置结构。懒创建,节省了不必要的内存浪费。

调用完 Core 模块的 create_conf 后,cycle->conf_ctx 数组如下图所示:

配置文件解析入口

ngx_init_cycle() 在准备好配置解析的上下文信息后,调用 ngx_conf_parse() 开始读取解析主配置文件。

ngx_conf_parse() 主干代码如下:

// 解析配置,参数filename可以是NULL
char* ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
{
    ....

    // 有文件名,解析文件
    if (filename) {
        /* open configuration file */
        fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
        ...

        // 保存当前的配置文件
        prev = cf->conf_file;

        // 将新配置文件设置当前配置文件
        cf->conf_file = &conf_file;

        // 取新配置文件相关的各种信息,例如大小
        if (ngx_fd_info(fd, &cf->conf_file->file.info) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
                          ngx_fd_info_n " \"%s\" failed", filename->data);
        }

        cf->conf_file->buffer = &buf;

        // 分配读取文件的缓冲区,不会一次性全部读取进内存
        buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);
        if (buf.start == NULL) {
            goto failed;
        }
        // 初始化缓冲区的指针
        buf.pos = buf.start;
        buf.last = buf.start;
        buf.end = buf.last + NGX_CONF_BUFFER;
        buf.temporary = 1;
        // 准备新的解析上下文信息ngx_conf_t
        cf->conf_file->file.fd = fd;
        cf->conf_file->file.name.len = filename->len;
        cf->conf_file->file.name.data = filename->data;
        cf->conf_file->file.offset = 0;
        cf->conf_file->file.log = cf->log;
        cf->conf_file->line = 1;

        // 标记当前是解析文件
        type = parse_file;

        ...

    } else if (cf->conf_file->file.fd != NGX_INVALID_FILE) {

        // 标记当前是解析配置块
        type = parse_block;

    } else {
        // 解析-g传递的命令行参数
        type = parse_param;
    }


    for ( ;; ) {
        // 从配置文件里读取token,保存在cf->args数组里
        rc = ngx_conf_read_token(cf);

        /*
         * ngx_conf_read_token() may return
         *
         *    NGX_ERROR             there is error
         *    NGX_OK                the token terminated by ";" was found
         *    NGX_CONF_BLOCK_START  the token terminated by "{" was found
         *    NGX_CONF_BLOCK_DONE   the "}" was found
         *    NGX_CONF_FILE_DONE    the configuration file is done
         */

        ...
            
        // 在所有模块里查找匹配的指令,进行解析
        rc = ngx_conf_handler(cf, rc);

        if (rc == NGX_ERROR) {
            goto failed;
        }
    }

failed:
    rc = NGX_ERROR;

done:
    // 配置文件解析完毕
    if (filename) {
        if (cf->conf_file->buffer->start) {
            ngx_free(cf->conf_file->buffer->start);
        }
        // 关闭配置文件
        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
                          ngx_close_file_n " %s failed",
                          filename->data);
            rc = NGX_ERROR;
        }

        // 恢复之前解析的配置文件,例如include指令,继续向下解析
        cf->conf_file = prev;
    }

    if (rc == NGX_ERROR) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}

在 Nginx 中,使用 ngx_conf_file_t 表示一个配置文件。

include 配置指令用来在当前配置文件中引入一个新的配置文件。在解析到 include "conf_file" 指令时,会暂停当前配置文件的解析,进入新配置文件 conf_file 中解析。

通过递归调用 ngx_conf_parse(),最终完成所有配置文件的解析。

ngx_conf_parse() 根据其第 2 参数 filename 文件名是否为空,进行不同的操作:

  • filename 不为 null,这时需要打开新的配置文件,for(;;) 循环读取新配置文件中的配置指令,并交给 ngx_conf_handler 处理。
  • filename 为 null,检查当前配置文件是不是个有效的配置文件(cf->conf_file->file.fd
    • 有效(fd != -1):说明解析到一个块 {},接下就是 for(;;) 循环读取块中指令,然后交给 ngx_conf_handler() 处理;
    • 无效(fd == -1):最后一种情况,是 -g 命令行参数传递的指令。

-g 命令行参数传递的指令也会被包装成一个配置文件 ngx_conf_file_t,然后交给 ngx_conf_parse(cf, NULL) 处理。

Q:什么时候会调用 ngx_conf_parse

  1. 程序刚启动时,会将主配置文件封装成 ngx_conf_file_t,然后调用 ngx_conf_parse(cf, "main_conf")
  2. 程序启动时,如果使用 -g 命令行参数传递了指令,会在 ngx_init_cycle() -> ngx_conf_param() 中,将指令包装成配置文件 ngx_conf_file_t(其中 fd 置为 -1)并保存到 cf->conf_file中,然后调用 ngx_conf_parse(cf, NULL) 处理。
  3. 如果解析到 include "new_conf" 指令(由 ngx_conf_handler() 在所有模块中检索),include 指令的处理函数 ngx_conf_include() 会调用 ngx_conf_parse(cf, "new_conf")
  4. 如果解析到块指令(如,events、http 等),会调用块指令的处理函数,块指令处理函数会调用 ngx_conf_parse(cf, NULL)。由于当前配置文件必然是个有效的配置文件,所以会进入块中,继续解析块中的指令。

接下来我们只关注配置文件中对 events{...} 块的解析。

解析 events{...}

我们以 events{...} 块中的 use epoll 为例讲解。

调用链:

main()
    |-> ngx_init_cycle()
            |-> ngx_conf_parse() // filename是主配置文件,for(;;)循环读取配置指令
                    |-> ngx_conf_handler()	// 将读取的指令字符串(events)与所有模块的预定义指令匹配
                            |-> ngx_events_block()	// 执行events指令的处理函数
                                    |-> ngx_conf_parse() //  filename为null,for(;;)循环读取events块中的配置指令
                                                |-> ngx_conf_handler // 将读取的指令字符串(use)与所有模块的预定义指令(use)匹配
                                                        |-> ngx_event_use // 执行use指令的处理函数
ngx_events_block()

ngx_events_block() 是块命令 events 的处理函数。

events 命令定义在 ngx_events_module(Core 模块),也是该模块唯一的指令,代码如下。

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
};
分配事件模块的配置数组

ngx_conf_parse() 解析到 events 命令后,会调用 ngx_conf_handler() -> ngx_events_block() 处理。文章开头列出了 Event 类型的模块多达 9 个。ngx_events_block() 在递归调用 ngx_conf_parse() 块中的指令(如 use epoll)之前,需要计算出所有 Event 模块的数量,并为他们分配一个指针数组。每个数组元素都是一个指向具体 Event 模块配置结构的指针。然后,调用各个事件模块的 create_conf,创建自己的配置结构,并将指针填充到配置数组中。

// 统计所有的事件模块数量,设置事件模块的ctx_index
ngx_event_max_module = ngx_count_modules(cf->cycle, NGX_EVENT_MODULE);

// ctx是void***,也就是void** *,即指向二维数组的指针
ctx = ngx_pcalloc(cf->pool, sizeof(void *));
if (ctx == NULL) {
    return NGX_CONF_ERROR;
}

// 分配存储事件模块配置的数组
*ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
if (*ctx == NULL) {
    return NGX_CONF_ERROR;
}

// 在cycle->conf_ctx里存储这个指针
*(void **) conf = ctx;

// 对每一个事件模块调用create_conf创建配置结构体
// 事件模块的层次很简单,没有多级,所以二维数组就够了
for (i = 0; cf->cycle->modules[i]; i++) {
    if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
        continue;
    }

    m = cf->cycle->modules[i]->ctx;

    // 调用create_conf创建配置结构体
    if (m->create_conf) {
        (*ctx)[cf->cycle->modules[i]->ctx_index] = m->create_conf(cf->cycle);
        if ((*ctx)[cf->cycle->modules[i]->ctx_index] == NULL) {
            return NGX_CONF_ERROR;
        }
    }
}

我们依然只关注 ngx_event_core_modulengx_epoll_module 这两个事件模块。

  • ngx_event_core_module 模块的 create_conf 函数指针实现是 ngx_event_core_create_conf
static ngx_event_module_t  ngx_event_core_module_ctx = {
    &event_core_name, 	// event_core模块的名字:"event_core"
    ngx_event_core_create_conf,            /* create configuration */
    ngx_event_core_init_conf,              /* init configuration */
    ...
};

继续跟进 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;
    ...

    return ecf;
}

创建了一个属于 ngx_event_core_module 模块的配置结构 ngx_event_conf_t,并对该结构进行强制初始化(-1)。

ngx_event_core_module 模块中定义了许多指令,如 useworker_connectionsaccept_mutex 等等。

ngx_event_conf_t 结构中的字段和其指令是一一对应的(名称类似)。你在 events{} 块中配置的指令参数,都会保存到指令所在事件模块的配置结构的相应字段中。如,worker_connections 1024; 会将 1024 保存到 ngx_event_conf_t 结构的 connections 字段中

所有事件模块的指令都需要放在 events{} 块中。

  • ngx_epoll_module 模块的 create_conf 函数指针实现是 ngx_epoll_create_conf
static ngx_event_module_t  ngx_epoll_module_ctx = {
    // epoll模块的名字"epoll"
    &epoll_name,
    // 创建配置结构体
    ngx_epoll_create_conf,          /* create configuration */
    // 初始化配置结构体
    ngx_epoll_init_conf,            /* init configuration */
    ...
};

同样的,创建了一个属于 ngx_epoll_module 模块的配置结构 ngx_epoll_conf_t,并对该结构进行了默认初始化(-1)。

static void *
ngx_epoll_create_conf(ngx_cycle_t *cycle)
{
    ngx_epoll_conf_t  *epcf;

    epcf = ngx_palloc(cycle->pool, sizeof(ngx_epoll_conf_t));
    if (epcf == NULL) {
        return NULL;
    }

    // 两个值都是数字,所以要置为-1
    epcf->events = NGX_CONF_UNSET;
    epcf->aio_requests = NGX_CONF_UNSET;

    return epcf;
}

根据 ngx_epoll_conf_t 可知,ngx_epoll_module 模块只定义了两个指令 epoll_eventsworker_aio_requests

解析 events 块中的指令

同样地,递归调用 ngx_conf_parse() 读取解析 events 块中的指令,这里就不重复说了。

// 暂存当前的解析上下文
pcf = *cf;

// 设置事件模块的新解析上下文
cf->ctx = ctx;
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;

// 递归解析事件相关模块
rv = ngx_conf_parse(cf, NULL);

ngx_conf_parse() 中,for(;;)循环读取到指令 use epoll; 后,交给 ngx_conf_handler() -> ngx_event_use() 处理。

static ngx_command_t  ngx_event_core_commands[] = {
    ...
    { ngx_string("use"),
      NGX_EVENT_CONF|NGX_CONF_TAKE1,
      ngx_event_use,
      0,
      0,
      NULL },
    ...
}
events 块解析完毕
// 递归解析事件相关模块
rv = ngx_conf_parse(cf, NULL);

// 恢复之前保存的解析上下文
*cf = pcf;

if (rv != NGX_CONF_OK) {
    return rv;
}

ngx_conf_parse() 返回后,说明对 events{...} 块的解析已经完成,这时,各个事件模块的配置结构也已经被配置文件中其所属的指令参数填充完毕。没有指定的,会在后面对其填充默认值。如,配置文件中没有 accept_mutex_delay,后面会被赋予 512ms 的默认值。

这时,cycle->conf_ctx 数组如下图所示:

填充配置默认值

对于没有在 events 块中出现的指令,在各个事件模块的 init_conf() 中都会给与其默认值。

// 解析完毕,需要初始化默认配置值
for (i = 0; cf->cycle->modules[i]; i++) {
    if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
        continue;
    }

    m = cf->cycle->modules[i]->ctx;

    if (m->init_conf) {
        rv = m->init_conf(cf->cycle,
                          (*ctx)[cf->cycle->modules[i]->ctx_index]);
        if (rv != NGX_CONF_OK) {
            return rv;
        }
    }
}

对于,ngx_event_core_modulengx_epoll_module 这两个事件模块,其 init_conf 函数指针分别对应 ngx_event_core_init_confngx_epoll_init_conf

ngx_event_core_init_conf定义在 _event/ngx_event.c_

static char* ngx_event_core_init_conf(ngx_cycle_t *cycle, void *conf)
{
    ngx_event_conf_t  *ecf = conf;

#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
    int                  fd;
#endif

    ngx_int_t            i;
    ngx_module_t        *module;
    ngx_event_module_t  *event_module;

    module = NULL;

// 测试epoll是否可用
#if (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)

    fd = epoll_create(100);

    // epoll调用可用,那么模块默认使用epoll
    if (fd != -1) {
        (void) close(fd);
        // epoll调用可用,那么模块默认使用epoll
        module = &ngx_epoll_module;

    } else if (ngx_errno != NGX_ENOSYS) {
        // epoll调用可用,那么模块默认使用epoll
        module = &ngx_epoll_module;
    }

#endif

#if (NGX_HAVE_DEVPOLL) && !(NGX_TEST_BUILD_DEVPOLL)

    module = &ngx_devpoll_module;

#endif

#if (NGX_HAVE_KQUEUE)

    module = &ngx_kqueue_module;

#endif

    // 如果epoll不可用,那么默认使用select
#if (NGX_HAVE_SELECT)

    if (module == NULL) {
        module = &ngx_select_module;
    }

#endif

    // 还没有决定默认的事件模型
    if (module == NULL) {
        // 遍历所有的事件模块
        for (i = 0; cycle->modules[i]; i++) {

            if (cycle->modules[i]->type != NGX_EVENT_MODULE) {
                continue;
            }

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

            // 跳过event_core模块
            if (ngx_strcmp(event_module->name->data, event_core_name.data) == 0)
            {
                continue;
            }

            // 使用数组里的第一个事件模块
            module = cycle->modules[i];
            break;
        }
    }

    // 最后还没有决定默认的事件模型,出错
    if (module == NULL) {
        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no events module found");
        return NGX_CONF_ERROR;
    }

    // nginx每个进程可使用的连接数量,即cycle里的连接池大小
    ngx_conf_init_uint_value(ecf->connections, DEFAULT_CONNECTIONS);

    // 如果没有使用worker_connections指令,在这里设置
    cycle->connection_n = ecf->connections;

    // 决定使用的事件模型,之前的module只作为默认值,如果已经使用了use则无效
    ngx_conf_init_uint_value(ecf->use, module->ctx_index);

    // 初始化使用的事件模块的名字
    event_module = module->ctx;
    ngx_conf_init_ptr_value(ecf->name, event_module->name->data);

    // 默认不接受多个请求,也就是一次只accept一个连接
    ngx_conf_init_value(ecf->multi_accept, 0);

    // 1.11.3之前默认使用负载均衡锁,之后默认关闭
    ngx_conf_init_value(ecf->accept_mutex, 0);

    // 默认负载均衡锁的等待时间是500毫秒
    ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500);

    return NGX_CONF_OK;
}

ngx_epoll_init_conf 定义在 _event/modules/ngx_epoll_module.c_

static char* ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf)
{
    ngx_epoll_conf_t *epcf = conf;
    // 如果没有使用epoll_events指令, epcf->events默认是512
    ngx_conf_init_uint_value(epcf->events, 512);
    ngx_conf_init_uint_value(epcf->aio_requests, 32);
    return NGX_CONF_OK;
}
收集监听端口

Nginx 使用 ngx_listening_t 表示一个监听端口。

cycle->listening 中保存了 Nginx 所有需要监听的端口。监听端口需要在配置文件中使用 listen 指定。配置文件 http{} 块示例如下:

http {
    ....
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    server {
        listen       8888 reuseport;
        server_name  localhost;
        ....
    }

    server {
       listen       9999;
       server_name  localhost;
    }
}

在配置文件的解析中,遇到 listen 指令后,会将其监听的端口信息暂存起来,直到整个 http 块解析完成后,将所有的搜集到的监听端口封装成一个个 ngx_listening_t 对象添加到 cycle->listening

注意:启用了 reuseport 的监听端口不在这里克隆(clone)。需要在配置文件解析完毕,在 Core 模块 ngx_events_moduleinit_conf 中做。

调用流程:

main()
    |-> ngx_init_cycle()
            |-> ngx_conf_parse() // filename是主配置文件,for(;;)循环读取配置指令
                    |-> ngx_conf_handler()	// 将读取的指令字符串(http)与所有模块的预定义指令匹配
                            |-> ngx_http_block() // http块处理函数
                                    |-> ngx_conf_parse(cf, NULL) 	// 解析http块
                                        ...
                                    |-> ngx_http_optimize_servers  // 根据解析好的监听端口信息,添加到cycle->listening
                                                    |-> ngx_http_init_listening
                                                                    |-> ngx_http_add_listening

ngx_http_add_listening() 根据前面解析好的监听端口信息,调用 ngx_create_listening() 创建一个 ngx_listening_t对象。然后将该对象添加到 cycle->listening 数组中。

如果 listen 指令指定了 reuseport,如,listen 8888 reuseport;,则 ngx_listening_t -> reuseport 会被置为 1,表示该监听端口启用了 reuseport 机制。

2.1.3 Core 模块配置默认初始化

ngx_init_cycle() -> ngx_conf_parse() 中解析完配置文件后,其他类型的模块都已经创建好了配置结构,填充了配置参数,没有指定的配置参数也都调用模块自己的 init_conf 配置了默认值。最后在 ngx_init_cycle() 中对所有 Core 模块,调用其 init_conf 初始化默认值。

// 其他类型的模块都已经配置好了,最后对Core模块配置初始化
// 如果有的参数没有明确配置,这里就调用init_conf设置默认值
for (i = 0; cycle->modules[i]; i++) {
    // 检查type,只处理core模块,数量很少
    if (cycle->modules[i]->type != NGX_CORE_MODULE) {
        continue;
    }

    //获取core模块的函数表
    module = cycle->modules[i]->ctx;

    // 调用core模块的初始化函数
    // 有的core模块可能没有这个函数,所以做一个空指针检查
    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;
        }
    }
}
2.1.3.1 ngx_event_init_conf(重要)

ngx_events_module 属于 Core 模块,它实现了 init_conf 函数指针,即 ngx_event_init_conf()

ngx_event_init_conf() 定义在 _event/ngx_event.c_

static char* ngx_event_init_conf(ngx_cycle_t *cycle, void *conf)
{
    // 1.15.2新增部分检查代码
#if (NGX_HAVE_REUSEPORT)
    ngx_uint_t        i;
    ngx_listening_t  *ls;
#endif

    // 要求必须有events{}配置块,1.15.2之前只有这一段
    if (ngx_get_conf(cycle->conf_ctx, ngx_events_module) == NULL) {
        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
                      "no \"events\" section in configuration");
        return NGX_CONF_ERROR;
    }

    // 检查连接数,需要大于监听端口数量
    if (cycle->connection_n < cycle->listening.nelts + 1) {

        /*
         * there should be at least one connection for each listening
         * socket, plus an additional connection for channel
         */

        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
                      "%ui worker_connections are not enough "
                      "for %ui listening sockets",
                      cycle->connection_n, cycle->listening.nelts);

        return NGX_CONF_ERROR;
    }

#if (NGX_HAVE_REUSEPORT)

    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {

        if (!ls[i].reuseport || ls[i].worker != 0) {
            continue;
        }

        // reuseport专用的函数,1.8.x没有
        // 拷贝了worker数量个的监听结构体, in ngx_connection.c
        // 从ngx_stream_optimize_servers等函数处转移过来
        if (ngx_clone_listening(cycle, &ls[i]) != NGX_OK) {
            return NGX_CONF_ERROR;
        }

        /* cloning may change cycle->listening.elts */
        ls = cycle->listening.elts;
    }

#endif

    return NGX_CONF_OK;
}

遍历所有监听端口(cycle->listening 数组),筛选出启用 reuseport 且还未绑定特定 Worker 的端口。调用 ngx_clone_listening() 为每个 Worker 复制独立的监听端口 ngx_listening_t 对象(它们都是绑定到相同的端口),这些 clone 的对象几乎都一样,只有 ngx_listening_t -> worker不同。例如,如果有 3 个 worker 进程,则根据已有的 ngx_listening_t(worker id 为 0) clone 出两个 ngx_listening_t,它们的 worker id 分别位 1 和 2。 不同的监听端口的 worker id 绑定到不同的 worker 上(在 fork Worker 进程时,也会为每一个 Worker 分配一个 worker id,编号从 0 开始,即 3 个 Worker 进程编号分别为 0、1 和 2)。

clone 出来的 ngx_listening_t 对象也会放到 cycle->listening 中。

reuseport 非常重要,它使得 Nginx 的并发性大大提高。

SO_REUSEPORT 是 Linux 3.9 内核引入的套接字选项,允许多个无关联的进程或线程绑定相同的 IP 和端口。其核心目标是通过内核层面对连接进行负载均衡,解决传统多进程/读线程模型中的性能瓶颈。网上资料很多,感兴趣的可以去深入了解下。

Master 进程的最主要工作是解析配置文件 nginx.conf。在 Nginx 中,用户主要通过 Nginx 配置文件 nginx.confevents 块来控制和调节事件模块的参数。下面是一个 nginx.conf 配置的示例:

master_process on;
worker_processes  3;

daemon on;

worker_rlimit_nofile 4096;

events {
    use epoll;
    multi_accept off;
    accept_mutex off;
    accept_mutex_delay 500ms;
    worker_connections  1024;
    epoll_events 1024;
}

http {
    ....
    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    server {
        listen       8888 reuseport;
        server_name  localhost;
        ....
    }

    server {
       listen       9999;
       server_name  localhost;
    }
}

2.1.4 端口

2.1.4.1 热更新

当向 Nginx 的 Master 进程发送 SIGUSR2 信号后,会触发 Nginx 的热更新。信号处理函数 ngx_signal_handler() 判断是 Master 进程且是 SIGUSR2 信号,会将原子的全局标志 ngx_change_binary 设置为 1。在 Master 的主循环函数 ngx_master_process_cycle() 中会不断检测该标志,如果 ngx_change_binary = 1,执行热更新操作。

void
ngx_master_process_cycle(ngx_cycle_t *cycle)
{
    ...
    // SIGUSR2,热更新nginx可执行文件,用于升级
    if (ngx_change_binary) {
        ngx_change_binary = 0; // 标志重新置为 0,防止反复触发
        ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
    
        // 函数在core/nginx.c里
        ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
    }
    ...
}

ngx_exec_new_binary() 会 fork 一个新的子进程,作为新 Master,然后新 Master 进程会立即执行 exec 函数加载最新的 Nginx 映像(从而实现平滑升级)。网上有很多文章都有介绍 Nginx 平滑升级的步骤。

ngx_exec_new_binary() 返回子进程的 PID,也就是新 Master 的 PID,并将其保存到全局变量 ngx_new_binary 中。

保存监听端口 fd 到环境变量 “NGINX”

ngx_exec_new_binary() 定义在 _core/nginx.c_

ngx_pid_t
ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv)
{
    char             **env, *var;
    u_char            *p;
    ngx_uint_t         i, n;
    ngx_exec_ctx_t     ctx;
    ngx_listening_t   *ls;

    ngx_memzero(&ctx, sizeof(ngx_exec_ctx_t));
    ctx.path = argv[0];
    ctx.name = "new binary process";
    ctx.argv = argv;
    ...

    // #define NGINX_VAR "NGINX"
    // 计算环境变量字符串的长度
    // 所有的监听端口,32位的数字
    // '+1'是分隔符';'
    // '+2'是'='和末尾的null
    //例如:"NGINX=8000;10890;"
    var = ngx_alloc(sizeof(NGINX_VAR)
                    + cycle->listening.nelts * (NGX_INT32_LEN + 1) + 2,
                    cycle->log);
    ...
    // 先拷贝变量名和'=',注意sizeof后面不用-1
    p = ngx_cpymem(var, NGINX_VAR "=", sizeof(NGINX_VAR));

    // 逐个追加文件描述符,后面接';'
    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {
        p = ngx_sprintf(p, "%ud;", ls[i].fd);
    }
    // 字符串最后是null
    *p = '\0';
    env[n++] = var;
    ...
    env[n] = NULL;
    ...
    ctx.envp = (char *const *) env;
    ...
    pid = ngx_execute(cycle, &ctx); // 创建子进程,执行exec
    ...
    ngx_free(env);
    ngx_free(var);

    return pid;
}

遍历 cycle->listening 数组,将所有打开的监听 socket fd 添加到环境变量 "NGINX"。例如,"NGINX=8;9;10;"

Q:为什么要传递监听 socket fd?

因为新 Master 无法重新绑定和监听端口(带有 SO_REUSEPORT 标志的除外),因为端口已经被绑定了,老的 Master 进程和 Worker 进程还都在监听端口,接收和处理连接。新 Master 只能复用打开监听端口,这样新老进程才能同时处理连接,从而实现平滑升级。

新 Master 是由老 Master 通过 fork + exec 创建的,因此它天然继承了打开监听端口。但是,由于,我们执行 execve 加载新的 Nginx 映像,导致操作监听 socket 的句柄,也就是那些文件描述符 fd 丢失了,因此需要将这些 fd 保存到环境变量 “NGINX”,然后传递给 execve 系统调用,execve 会将这些环境变量参数设置为子进程的环境变量。

当 execve 执行完成,新 Master 进程启动,它就可以读取 “NGINX” 环境变量,获取这些 fd。有了 fd,就可以重新操作打开的监听 socket。

创建新 Master

将环境变量 "NGINX" 添加到上下文 ctx,然后调用 ngx_execute()

ngx_pid_t
ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx)
{
    // 产生进程执行ngx_execute_proc
    // 不与worker发生关系,没有channel通信
    return ngx_spawn_process(cycle, ngx_execute_proc, ctx, ctx->name,
                             NGX_PROCESS_DETACHED);
}
ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn) {
    ...
    // 调用fork产生子进程
    pid = fork();
    switch (pid) {
    // pid=-1,表示fork子进程出错
    case -1:
        ...

    // 子进程中,pid=0
    case 0:
        ngx_parent = ngx_pid;
        ngx_pid = ngx_getpid();
        proc(cycle, data); // ngx_execute_proc
        break;

    // 父进程中,pid=子进程PID
    default:
        break;
    }
    ....
    return pid;
}

ngx_execute_proc 定义在 _os/unix/ngx_process.c_

static void
ngx_execute_proc(ngx_cycle_t *cycle, void *data)
{
    ngx_exec_ctx_t  *ctx = data;

    // 系统调用execve()
    if (execve(ctx->path, ctx->argv, ctx->envp) == -1) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "execve() failed while executing %s \"%s\"",
                      ctx->name, ctx->path);
    }

    exit(1);
}

新 Master 进程立即执行 execve 系统调用,注意,ctx->envp 中保存了环境变量 "NGINX"。新 Master 进程在启动后,可以接收到 execve 系统调用传递的参数和环境变量。

新 Master 解析环境变量"NGINX"

新 Master 进程重走一遍 Nginx 的初始化流程。在 main() -> ngx_add_inherited_sockets()中,解析传递过来的环境变量 "NGINX"

// 检查NGINX环境变量,获取之前监听的socket
static ngx_int_t
ngx_add_inherited_sockets(ngx_cycle_t *cycle)
{
    u_char           *p, *v, *inherited;
    ngx_int_t         s;
    ngx_listening_t  *ls;

    // 获取环境变量,NGINX_VAR定义在nginx.h,值是"NGINX"
    inherited = (u_char *) getenv(NGINX_VAR);

    // 无变量则直接退出函数
    if (inherited == NULL) {
        return NGX_OK;
    }

    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                  "using inherited sockets from \"%s\"", inherited);

    // 清空cycle的监听端口数组
    if (ngx_array_init(&cycle->listening, cycle->pool, 10,
                       sizeof(ngx_listening_t))
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    // 从环境变量字符串里切分出socket fd
    // 逐个添加进cycle->listening数组
    for (p = inherited, v = p; *p; p++) {
        // 分隔符是 : 或 ;
        if (*p == ':' || *p == ';') {

            // 把字符串转换成整数,即socket描述符
            s = ngx_atoi(v, p - v);
            if (s == NGX_ERROR) {
                ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
                              "invalid socket number \"%s\" in " NGINX_VAR
                              " environment variable, ignoring the rest"
                              " of the variable", v);
                break;
            }

            // 跳过分隔符
            v = p + 1;

            // 向数组中添加一个监听端口
            ls = ngx_array_push(&cycle->listening);
            if (ls == NULL) {
                return NGX_ERROR;
            }

            ngx_memzero(ls, sizeof(ngx_listening_t)); // 清0

            // 设置为刚获得的socket fd
            ls->fd = (ngx_socket_t) s;
            ls->inherited = 1;
        }
    }

    // v和p是环境变量字符串的指针,最后必须都到末尾
    // 否则格式有错误,但只记录日志,正常添加socket描述符
    if (v != p) {
        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
                      "invalid socket number \"%s\" in " NGINX_VAR
                      " environment variable, ignoring", v);
    }

    // 设置继承socket fd的全局标志位
    ngx_inherited = 1;

    // in ngx_connection.c
    // 根据传递过来的socket描述符,使用系统调用获取之前设置的参数
    // 填入ngx_listeing_t结构体
    return ngx_set_inherited_sockets(cycle);
}

Nginx 将监听端口封装成 ngx_listeing_t,在 ngx_add_inherited_sockets() 中只设置了 ngx_listeing_tfdinherited 字段,监听端口的其他信息是没有的。因此,接下来最重要的便是根据 fd 调用相关系统调用获取监听 socket 的更多信息。这个工作是 ngx_set_inherited_sockets()ngx_add_inherited_sockets() 返回前完成的。

ngx_set_inherited_sockets() 函数根据 fd 获取监听 socket 信息(如调用 getsockname,获取监听 socket 地址)时如果出错,则可能这个打开的监听 socket 已经损坏,设置对应 ngx_listeing_t 的字段 ignore 为 1, 后面遍历过程中,会跳过该监听 socket。

2.1.4.2 打开端口

刚到本节你可能会蒙一下!不是复用了继承的打开端口了嘛,怎么还要打开?容我细细道来。

首先复用打开端口是在热更新下必须的。但是,当 Nginx 首次启动时,所有端口都没有被绑定,因此需要进行绑定。其次,即使热更新,新 Master 进程和老 Master 进程监听的端口也不一定完全一致,如果新 Master 读取新的配置文件,里面新增了监听端口,这时 Master 不也得打开新增的端口。

当然,还有很多细节需要处理,比如,打开的监听端口没有使用 reuseport 机制,而新 Master 读取的新配置文件中该打开的监听端口添加了 reuseport 机制,这时,我们就得对这个已经打开的监听端口 socket 调用 setsockopt(),设置上 SO_REUSEPORT 选项,然后还得再以SO_REUSEPORT 选项打开 Worker 数量 -1 个相同端口。这样,每个 Worker 进程才能都分配一个。

打开监听端口的逻辑也是在 ngx_init_cycle() 中完成的,不过它位于配置文件解析完成之后。

复用继承来的打开端口
/* handle the listening sockets */

// 热更新,新Master从环境变量中获取的打开监听socket fd位于old_cycle->listening
if (old_cycle->listening.nelts) {
    ls = old_cycle->listening.elts;
    for (i = 0; i < old_cycle->listening.nelts; i++) {
        ls[i].remain = 0;
    }

    // 新的端口配置信息位于cycle->listening,配置文件解析阶段填充
    nls = cycle->listening.elts;
    for (n = 0; n < cycle->listening.nelts; n++) {

        // 新端口与打开的监听端口匹配,新端口直接复用打开监听socket fd
        for (i = 0; i < old_cycle->listening.nelts; i++) {
            // 如果之前ngx_set_inherited_sockets获取打开监听socket参数时出错就会设置ignore
            if (ls[i].ignore) {
                continue;
            }

            // 循环开始前都不是remain
            if (ls[i].remain) {
                continue;
            }

            // 必须是相符的协议,tcp/udp
            if (ls[i].type != nls[n].type) {
                continue;
            }

            // 新端口与继承来的打开端口匹配成功
            if (ngx_cmp_sockaddr(nls[n].sockaddr, nls[n].socklen,
                                 ls[i].sockaddr, ls[i].socklen, 1)
                == NGX_OK)
            {
                // 复用继承而来的打开监听socket
                nls[n].fd = ls[i].fd;
                // 继承而来的打开监听端口的inherited都被置为1
                nls[n].inherited = ls[i].inherited; 
                // 保存继承的打开端口的ngx_listeing_t对象
                nls[n].previous = &ls[i];

                // 置remain标记,已经找到,不需要再处理
                ls[i].remain = 1;

                // backlog不同,重新调用listen设置
                // listen可以调用多次,从第2次开始就只是简单地设置backlog
                if (ls[i].backlog != nls[n].backlog) {
                    nls[n].listen = 1; // 标记新端口需要重新listen
                }

                ...

#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)

                if (ls[i].deferred_accept && !nls[n].deferred_accept) {
                    nls[n].delete_deferred = 1;

                } else if (ls[i].deferred_accept != nls[n].deferred_accept)
                {
                    nls[n].add_deferred = 1;
                }
#endif

#if (NGX_HAVE_REUSEPORT)
                // 新端口使用了reuseport而继承来的打开监听端口没有
                if (nls[n].reuseport && !ls[i].reuseport) {
                    // 标记继承来的打开监听socket需要添加SO_REUSRPORT选项
                    nls[n].add_reuseport = 1; 
                }
#endif

                break;
            }
        }   // for old_cycle

        if (nls[n].fd == (ngx_socket_t) -1) {
            nls[n].open = 1; // 标记该新端口需要打开

            ...
                
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
            if (nls[n].deferred_accept) {
                nls[n].add_deferred = 1;
            }
#endif
        }
    }

} else {
    // 没有继承来的打开端口列表
    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {
        // 所有新端口都需要设置为打开
        ls[i].open = 1;

        ...
            
        //支持defered特性则可以使用
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
        if (ls[i].deferred_accept) {
            ls[i].add_deferred = 1;
        }
#endif
    }
}

在处理完了新端口复用继承来的打开端口后,接下来调用 ngx_open_listening_sockets() 正式执行端口打开操作。

打开端口
ngx_int_t
ngx_open_listening_sockets(ngx_cycle_t *cycle)
{
    int               reuseaddr;
    ngx_uint_t        i, tries, failed;
    ngx_err_t         err;
    ngx_log_t        *log;
    ngx_socket_t      s;
    ngx_listening_t  *ls;

    // 默认所有监听端口都使用REUSEADDR选项
    reuseaddr = 1;
#if (NGX_SUPPRESS_WARN)
    failed = 0;
#endif

    log = cycle->log;

    /* TODO: configurable try number */
    // 失败尝试5次
    for (tries = 5; tries; tries--) {
        failed = 0;

        /* for each listening socket */

        // 遍历监听端口链表
        // 如果设置了reuseport,那么一个端口会有worker个克隆的结构体
        ls = cycle->listening.elts;
        for (i = 0; i < cycle->listening.nelts; i++) {

            if (ls[i].ignore) {
                continue;
            }
#if (NGX_HAVE_REUSEPORT)
            // add_reuseport=1说明新端口复用了继承来的打开端口(没有启用reuseport)
            // 而新端口启用了reuseport,因此,需要给继承来的打开端口添加SO_REUSEPORT选项
            // 由于继承来的打开端口没有启用reuseport,故只有一个监听scoket实例
            // 而新端口需要worker个监听socket实例,所以worker-1个socket实例需要打开
            if (ls[i].add_reuseport) {
                /*
                 * to allow transition from a socket without SO_REUSEPORT
                 * to multiple sockets with SO_REUSEPORT, we have to set
                 * SO_REUSEPORT on the old socket before opening new ones
                 */
                int  reuseport = 1;

                ....

                if (setsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT,
                               (const void *) &reuseport, sizeof(int))
                    == -1)
                {
                    ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
                                  "setsockopt(SO_REUSEPORT) %V failed, ignored",
                                  &ls[i].addr_text);
                }

                // 标志位清零
                ls[i].add_reuseport = 0;
            }
#endif
            // 已经打开的端口不再处理
            if (ls[i].fd != (ngx_socket_t) -1) {
                continue;
            }

            // 从旧Master进程继承过来的,已经打开,所以也不需要再处理
            if (ls[i].inherited) {

                /* TODO: close on exit */
                /* TODO: nonblocking */
                /* TODO: deferred accept */

                continue;
            }

            // 创建监听socket
            s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);

            if (s == (ngx_socket_t) -1) {
                ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                              ngx_socket_n " %V failed", &ls[i].addr_text);
                return NGX_ERROR;
            }

            // 默认所有监听端口都添加SO_REUSEADDR选项
            if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
                           (const void *) &reuseaddr, sizeof(int))
                == -1)
            {
                ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                              "setsockopt(SO_REUSEADDR) %V failed",
                              &ls[i].addr_text);

                if (ngx_close_socket(s) == -1) {
                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                                  ngx_close_socket_n " %V failed",
                                  &ls[i].addr_text);
                }

                return NGX_ERROR;
            }

#if (NGX_HAVE_REUSEPORT)
            // 当使用 -t 命令行选项时,ngx_test_config=1
            if (ls[i].reuseport && !ngx_test_config) {
                int  reuseport;

                reuseport = 1;

                ...
                // 设置SO_REUSEPORT选项
                if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT,
                               (const void *) &reuseport, sizeof(int))
                    == -1)
                {
                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                                  "setsockopt(SO_REUSEPORT) %V failed",
                                  &ls[i].addr_text);

                    if (ngx_close_socket(s) == -1) {
                        ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                                      ngx_close_socket_n " %V failed",
                                      &ls[i].addr_text);
                    }

                    return NGX_ERROR;
                }
            }
#endif
            ...
                
            /* TODO: close on exit */
            // IOCP是Windows提供的一种高性能、可扩展的异步I/O模型。这种模型基于事件驱动。
            // 对于非IOCP,都需要将监听端口设置为非阻塞
            if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {

                // 监听端口设置为非阻塞,使用ioctl(s,FIONBIO,1)
                if (ngx_nonblocking(s) == -1) {
                    ...
                }
            }

            ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0,
                           "bind() %V #%d ", &ls[i].addr_text, s);

            // bind绑定地址端口
            if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
                ...
            }

            ...

            // 检查是否是tcp,即SOCK_STREAM
            if (ls[i].type != SOCK_STREAM) {
                // 如果不是tcp,即udp,那么无需监听
                ls[i].fd = s;

                // 继续处理下一个监听结构体
                continue;
            }

            // 开始监听,设置backlog
            if (listen(s, ls[i].backlog) == -1) {
                ...
            }

            // 设置已经监听标志
            ls[i].listen = 1;

            // 设置监听socket文件描述符
            ls[i].fd = s;
        }

        ...
    }

    ...
}

所有监听端口都添加 SO_REUSEADDR 选项。

所有监听端口都设置为非阻塞模式

除了复用继承来的打开端口外(可能会添加 SO_REUSEPORT 选项),其余的端口都会按照标准 socket()bind()listen() 顺序打开端口。

对于打开失败的情况,该函数会重试 5 次。

配置监听端口的参数

ngx_configure_listening_sockets 会设置 rcvbufsndbufkeepalive 等。逻辑很简单,就不列出来了。

2.1.4.3 关闭继承来的打开端口

对于没有被新端口复用的继承来的打开端口,会在 ngx_init_cycle() 中进行关闭。

没有使用的资源需要及时关闭,避免资源迟迟无法释放。

/* close the unnecessary listening sockets */
ls = old_cycle->listening.elts;
for (i = 0; i < old_cycle->listening.nelts; i++) {

    // 设置了remain,说明被新端口复用了,不能关闭
    // 端口没有打开,无需关闭
    if (ls[i].remain || ls[i].fd == (ngx_socket_t) -1) {
        continue;
    }

    if (ngx_close_socket(ls[i].fd) == -1) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
                      ngx_close_socket_n " listening socket on %V failed",
                      &ls[i].addr_text);
    }
    
    ...
}

2.1.5 所有模块初始化

ngx_init_modulesngx_init_cycle 中调用。ngx_init_modules 调用所有模块的 init_module 函数指针,初始化模块。

ngx_int_t
ngx_init_modules(ngx_cycle_t *cycle)
{
    ngx_uint_t  i;

    // 注意不使用全局的ngx_modules数组,而是使用cycle里的
    for (i = 0; cycle->modules[i]; i++) {

        // 调用所有模块的init_module函数指针,初始化模块
        if (cycle->modules[i]->init_module) {
            if (cycle->modules[i]->init_module(cycle) != NGX_OK) {
                return NGX_ERROR;
            }
        }
    }

    return NGX_OK;
}

动态模块在解析完配置文件后就已经全部加载完毕。因此这里所有模块(包括静态、动态模块)都会调用 init_module 初始化。

与事件相关的 Core 模块 ngx_events_module 没有实现 init_module 函数指针,跳过。

ngx_epoll_module 事件模块也没有实现 init_module 函数指针,也跳过。

2.1.5.1 ngx_event_core_module 模块的初始化

事件模块 ngx_event_core_moduleinit_module 函数指针的实现是 ngx_event_module_init 函数。该函数主要做了下面三个工作:

  • (1)获取 Core 模块配置结构体中的时间精度 timer_resolution,用于周期性更新时间缓存。
  • (2)调用 getrlimit(2) 方法,检查连接数是否超过系统的资源限制。
  • (3)利用 mmap(2) 分配一块共享内存,存储负载均衡锁(ngx_accept_mutex)、连接计数器(ngx_connection_counter)等。
// 在ngx_init_cycle里调用,fork子进程之前
// 创建共享内存,存放负载均衡锁和统计用的各种原子变量
static ngx_int_t
ngx_event_module_init(ngx_cycle_t *cycle)
{
    void              ***cf;
    u_char              *shared;
    size_t               size, cl;
    ngx_shm_t            shm;
    ngx_time_t          *tp;
    ngx_core_conf_t     *ccf;
    ngx_event_conf_t    *ecf;

    // events模块的配置结构体,实际上是一个存储void*指针的数组
    cf = ngx_get_conf(cycle->conf_ctx, ngx_events_module);

    // event_core模块的配置结构体,从数组cf里按序号查找
    ecf = (*cf)[ngx_event_core_module.ctx_index];

    // 上面的两行代码相当于:
    // ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module)

    if (!ngx_test_config && ngx_process <= NGX_PROCESS_MASTER) {
        ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                      "using the \"%s\" event method", ecf->name);
    }

    // core模块的配置结构体
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

    // 获取core配置的更新时间缓存的精度,保存到全局变量ngx_timer_resolution
    ngx_timer_resolution = ccf->timer_resolution;

    // Unix/Linux专用代码
#if !(NGX_WIN32)
    {
    ngx_int_t      limit;
    struct rlimit  rlmt;

    // Linux系统调用getrlimit获取内核对进程资源的限制
    // RLIMIT_NOFILE,进程可打开的最大文件描述符数量,超出将产生EMFILE错误
    if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {

        // 系统调用失败则记录alert级别日志
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "getrlimit(RLIMIT_NOFILE) failed, ignored");

    } else {
        // 成功获取进程的最大打开文件描述符数量
        //
        // rlmt.rlim_cur是内核资源的软限制
        // events块中的配置指令worker_connections设置的连接数不能超过内核软限制
        // 也不能超过全局块中的配置指令rlimit_nofile施加的限制(如果设置了的话)
        if (ecf->connections > (ngx_uint_t) rlmt.rlim_cur
            && (ccf->rlimit_nofile == NGX_CONF_UNSET
                || ecf->connections > (ngx_uint_t) ccf->rlimit_nofile))
        {
            // 如果超过了限制,记录警告级别日志
            limit = (ccf->rlimit_nofile == NGX_CONF_UNSET) ?
                         (ngx_int_t) rlmt.rlim_cur : ccf->rlimit_nofile;

            ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
                          "%ui worker_connections exceed "
                          "open file resource limit: %i",
                          ecf->connections, limit);
        }
    }
    }
#endif /* !(NGX_WIN32) */


    // 如果非master/worker模型,即只启动一个Master进程处理所有事情,那么就没必要使用负载均衡锁
    if (ccf->master == 0) {
        return NGX_OK;
    }

    // 已经有了负载均衡锁,已经初始化过了,就没必要再做操作
    if (ngx_accept_mutex_ptr) {
        return NGX_OK;
    }


    /* cl should be equal to or greater than cache line size */

    // cl是一个基本长度,可以容纳原子变量
    // 对齐到cache line,操作更快
    cl = 128;

    // 最基本的三个:负载均衡锁,连接计数器,
    size = cl            /* ngx_accept_mutex */
           + cl          /* ngx_connection_counter */
           + cl;         /* ngx_temp_number */

    // 其他统计用的原子变量
#if (NGX_STAT_STUB)

    size += cl           /* ngx_stat_accepted */
           + cl          /* ngx_stat_handled */
           + cl          /* ngx_stat_requests */
           + cl          /* ngx_stat_active */
           + cl          /* ngx_stat_reading */
           + cl          /* ngx_stat_writing */
           + cl;         /* ngx_stat_waiting */

#endif

    // 创建共享内存,存放负载均衡锁和统计用的原子变量
    // 因为内存很小,而且仅用做统计,比较简单
    // 所以不用slab管理
    shm.size = size;
    ngx_str_set(&shm.name, "nginx_shared_zone");
    shm.log = cycle->log;

    // mmap分配一块匿名共享可读写的内存
    if (ngx_shm_alloc(&shm) != NGX_OK) {
        return NGX_ERROR;
    }

    // shared是共享内存的地址指针
    shared = shm.addr;

    // 负载均衡锁存放在共享内存的起始地址处
    ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;

    // accept_mutex锁的spin是-1,表明不使用信号量
    // worker进程使用ngx_shmtx_trylock()获取accept_mutex锁
    ngx_accept_mutex.spin = (ngx_uint_t) -1;

    // 初始化accept_mutex锁
    if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,
                         cycle->lock_file.data)
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    // 连接计数器
    ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);

    // 计数器置1
    (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);

    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "counter: %p, %uA",
                   ngx_connection_counter, *ngx_connection_counter);

    // 临时文件用
    ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl);

    tp = ngx_timeofday();

    // 随机数,每个进程不同
    ngx_random_number = (tp->msec << 16) + ngx_pid;

#if (NGX_STAT_STUB)

    ngx_stat_accepted = (ngx_atomic_t *) (shared + 3 * cl);
    ngx_stat_handled = (ngx_atomic_t *) (shared + 4 * cl);
    ngx_stat_requests = (ngx_atomic_t *) (shared + 5 * cl);
    ngx_stat_active = (ngx_atomic_t *) (shared + 6 * cl);
    ngx_stat_reading = (ngx_atomic_t *) (shared + 7 * cl);
    ngx_stat_writing = (ngx_atomic_t *) (shared + 8 * cl);
    ngx_stat_waiting = (ngx_atomic_t *) (shared + 9 * cl);

#endif

    return NGX_OK;
}

2.2 Worker 进程对事件模块的初始化

2.2.1 创建 Worker 进程

Nginx 启动进程调用 ngx_init_cycle 完成一系列初始化之后,还会进行如下操作:

  • 令全局变量 ngx_cycle 指向创建好的新 cycle。
  • 判断 Nginx 是单 Master 模式还是 Master/Worker 模式。
  • 注册信号处理函数。
  • 守护进程化
  • 将进程 PID 写入 nginx.pid 文件。
  • 根据 Nginx 进程模式执行不同操作:
    • 单 Master 模式:执行 ngx_single_process_cycle(不关注)
    • Master/Worker 模式:执行 ngx_master_process_cycle

ngx_master_process_cycle 函数调用 ngx_start_worker_processes 创建子进程:

#define NGX_PROCESS_RESPAWN -3
ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN);
static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
    ngx_int_t  i;

    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");

    for (i = 0; i < n; i++) {

        // os/unix/ngx_process.c产生进程,执行ngx_worker_process_cycle
        // 创建的进程都在ngx_processes数组里
        // 定义在os/unix/ngx_process.c
        // ngx_process_t  ngx_processes[NGX_MAX_PROCESSES];
        ngx_spawn_process(cycle, ngx_worker_process_cycle,
                          (void *) (intptr_t) i, "worker process", type);

        ngx_pass_open_channel(cycle);
    }
}

配置指令 worker_processes 指定了 Worker 进程的数量。

ngx_start_worker_processes 循环调用 ngx_spawn_process 创建 worker_processes 个子进程。

ngx_pid_t
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn)
{
    ...

    // 调用fork产生子进程
    pid = fork();

    switch (pid) {

    // -1产生子进程出错
    case -1:
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "fork() failed while spawning \"%s\"", name);
        ngx_close_channel(ngx_processes[s].channel, cycle->log);
        return NGX_INVALID_PID;

    // 0是子进程,开始执行worker进程的核心函数
    // ngx_worker_process_cycle,即无限循环处理事件
    case 0:
        // 1.14.0, 子进程保存父进程的pid,即master的pid
        ngx_parent = ngx_pid;

        // worker进程重新获取自己的进程id
        ngx_pid = ngx_getpid();

        // 这里是子进程的真正工作,无限循环
        // proc = ngx_worker_process_cycle
        // data = (void *) (intptr_t) i,即 worker id
        // worker id 在绑定reuseport端口时会用到
        proc(cycle, data);
        break;

    // 父进程得到子进程的pid
    default:
        break;
    }

    ...

    return pid;
}

2.2.2 Worker 开始执行

进入 Worker 进程后,首先执行的就是 ngx_worker_process_cycle,该函数是 worker 进程的主循环函数,该函数首先会调用 ngx_worker_process_init 函数完成 Worker 的初始化,后就会进入到一个无限循环中,持续监听处理请求。

ngx_worker_process_cycle 函数代码如下:

// data实际上是worker进程的序号(0、1、2等), (void *) (intptr_t) i
static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    // 把data再转换为进程序号
    ngx_int_t worker = (intptr_t) data;

    // 设置进程状态
    ngx_process = NGX_PROCESS_WORKER;

    // 这里把进程序号赋值给全局变量ngx_worker,REUSEPORT端口绑定时需要
    ngx_worker = worker;

    // 读取核心配置,设置cpu优先级,core dump信息,unix运行的group/user
    // 切换工作路径,根据pid设置随机数种子
    // 调用所有模块的init_process进行初始化
    ngx_worker_process_init(cycle, worker);

    // 设置worker进程名字
    ngx_setproctitle("worker process");

    // 无限循环,处理事件和信号
    for ( ;; ) {

        ...

        // 处理事件的核心函数, event模块里
        // 处理socket读写事件和定时器事件
        // 获取负载均衡锁,监听端口接受连接
        // 调用epoll模块的ngx_epoll_process_events
        // 然后处理超时事件和在延后队列里的所有事件
        // nginx大部分的工作量都在这里
        ngx_process_events_and_timers(cycle);

        ...

    } // 无限循环,处理事件和信号
}

省略的部分都是信号处理代码。

2.2.2.1 Worker 初始化

在进入无限循环之前,需要先调用 ngx_worker_process_init 进行 Worker 的相关初始化。

ngx_worker_process_init 函数做了如下一些事:

  • 设置 CPU 优先级,core dump 信息,Unix/Linux 运行的 group/user;
  • 设置 Worker 进程的 CPU 亲和性;
  • 切换工作路径;
  • 解除信号阻塞(子进程继承了父进程的阻塞信号);
  • 根据 PID 设置随机数种子;
  • 调用所有模块的 init_process 进行初始化。

ngx_worker_process_init 函数如下所示:

static void
ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
{
    ...
    // 调用所有模块的init_process,模块进程初始化hook
    for (i = 0; cycle->modules[i]; i++) {
        if (cycle->modules[i]->init_process) {
            if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
                /* fatal */
                exit(2);
            }
        }
    }
    ...
}

ngx_event_core_module 事件模块实现了 init_process 函数指针,即 ngx_event_process_init

ngx_epoll_module 模块没有实现 init_process 函数指针。

在正式介绍 ngx_event_process_init 函数前,先介绍两个结构体。

ngx_event_t

ngx_event_t 结构体。Nginx中,事件会使用 ngx_event_t 结构体表示:

// nginx事件模块的核心结构体
// 表示一个nginx事件(读/写/超时)
// data表示事件相关的对象,通常是ngx_connection_t,用c = ev->data;
// 重要的成员是handler,即事件发生时调用的函数
struct ngx_event_s {
    // 事件相关的对象,通常是ngx_connection_t
    // 在多线程通知里是ngx_event_handler_pt,即通知回调函数
    void            *data;

    // 写事件,也就是说tcp连接是写状态,可以发送数据
    // 如果是0,意味着这个事件是读事件
    // ngx_connection.c:ngx_get_connection里设置
    unsigned         write:1;

    // 监听状态标志位,只有listening相关的事件才置此标志位
    unsigned         accept:1;

    /* used to detect the stale events in kqueue and epoll */
    // 检测事件是否失效
    // 存储在epoll数据结构体里的指针低位
    // 在ngx_get_connection里获取空闲连接时,这个标志位会取反
    // 这样如果连接失效,那么instance就会不同
    // 判断逻辑在ngx_epoll_module.c:ngx_epoll_process_events
    unsigned         instance:1;

    /*
     * the event was passed or would be passed to a kernel;
     * in aio mode - operation was posted.
     */
    // 事件是否是活跃的,也就是说已经添加进了epoll关注
    unsigned         active:1;

    // epoll无意义
    unsigned         disabled:1;

    /* the ready event; in aio mode 0 means that no operation can be posted */
    // 事件已经就绪,也就是说有数据可读或者可以发送数据
    // 在读写操作完成后会置ready=0
    unsigned         ready:1;

    // epoll无意义
    unsigned         oneshot:1;

    /* aio operation is complete */
    // 异步操作的完成标志,用于aio和多线程
    unsigned         complete:1;

    // 当前的字节流已经结束即eof,不会再有数据可读
    // 如果recv() == 0 ,客户端关闭连接,那么置此标记
    unsigned         eof:1;

    // 发生了错误
    unsigned         error:1;

    // 事件是否已经超时
    // 由ngx_event_expire_timers遍历定时器红黑树,找出所有过期的事件设置此标志位
    unsigned         timedout:1;

    // 事件是否在定时器里
    // ngx_add_timer加入定时器时设置
    // 处理完定时器事件后清除标记
    unsigned         timer_set:1;

    // 需要延迟处理,用于限速,nginx会暂不写数据
    // 参考ngx_http_write_filter_module.c
    unsigned         delayed:1;

    // 延迟接收请求,即只有客户端真正发来数据时内核才会触发accept
    // 可以提高运行效率
    unsigned         deferred_accept:1;

    /* the pending eof reported by kqueue, epoll or in aio chain operation */
    unsigned         pending_eof:1;

    // 事件是否已经加入延后处理队列中,可以加快事件的处理速度
    // 操作函数宏ngx_post_event/ngx_delete_posted_event
    // ngx_posted_accept_events/ngx_posted_events
    unsigned         posted:1;

    unsigned         closed:1;

    /* to test on worker exit */
    unsigned         channel:1;
    unsigned         resolver:1;

    // 在进程退出时定时器是否可以取消,不能,则等定时期过期才可以退出进程
    unsigned         cancelable:1;

    // 是否尽可能多地接受请求建立连接,即multi_accept
    // 1.11.x后增加新用途,在接收数据时标记是否可用
    // 早期(< 1.17.4)这里使用了bit field,只能存储0/1,节约内存
    int              available;

    // 重要!!
    // 事件发生时调用的函数
    // ngx_core.h:typedef void (*ngx_event_handler_pt)(ngx_event_t *ev);
    // tcp/http接受连接时的回调是ngx_event_accept
    // udp接受连接时的回调是ngx_event_recvmsg
    ngx_event_handler_pt  handler;

    // 在epoll通知机制里用作简单的计数器ngx_epoll_notify_handler
    ngx_uint_t       index;

    // 日志对象
    ngx_log_t       *log;

    // 红黑树节点成员,用于把事件加入定时器
    // 判断事件超时用
    ngx_rbtree_node_t   timer;

    /* the posted queue */
    // 队列成员,加入延后处理的队列
    // ngx_posted_accept_events/ngx_posted_events
    ngx_queue_t      queue;

    ...
};
ngx_connection_t

ngx_connection_t 结构体代表一个 Nginx 连接。

// 连接结构体,表示nginx里的一个tcp连接
// 每个连接都有一个读事件和写事件,使用数组序号对应
// nginx里的连接对象都保存在ngx_cycle_t::connections数组里
struct ngx_connection_s {
    // data成员有两种用法
    // 未使用(空闲)时作为链表的后继指针,连接在ngx_cycle_t::free_connections里
    // 在http模块里保存ngx_http_request_t对象,标记连接对应的http请求
    // 在stream模块里保存ngx_stream_session_t对象
    void               *data;

    // 连接对应的读事件,存储在ngx_cycle_t::read_events
    ngx_event_t        *read;

    // 连接对应的写事件,存储在ngx_cycle_t::write_events
    ngx_event_t        *write;

    // 连接的socket描述符(句柄)
    // 需使用此描述符才能收发数据
    ngx_socket_t        fd;

    // 接收数据的函数指针
    // ngx_event_accept.c:ngx_event_accept()里设置为ngx_recv
    // ngx_posix_init.c里初始化为linux的底层接口
    ngx_recv_pt         recv;

    // 发送数据的函数指针
    // ngx_event_accept.c:ngx_event_accept()里设置为ngx_send
    // ngx_posix_init.c里初始化为linux的底层接口
    ngx_send_pt         send;

    ngx_recv_chain_pt   recv_chain;

    // linux下实际上是ngx_writev_chain.c:ngx_writev_chain
    //
    // 发送limit长度(字节数)的数据
    // 如果事件not ready,即暂不可写,那么立即返回,无动作
    // 要求缓冲区必须在内存里,否则报错
    // 最后返回消费缓冲区之后的链表指针
    // 发送出错、遇到again、发送完毕,这三种情况函数结束
    // 返回的是最后发送到的链表节点指针
    //
    // 发送后需要把已经发送过的节点都回收,供以后复用
    ngx_send_chain_pt   send_chain;

    // 连接对应的ngx_listening_t监听对象
    // 通过这个指针可以获取到监听端口相关的信息
    // 反过来可以操作修改监听端口
    ngx_listening_t    *listening;

    // 连接上已经发送的字节数
    // ngx_send.c里发送数据成功后增加
    // 在32位系统里最大4G,可以定义宏_FILE_OFFSET_BITS=64
    off_t               sent;

    // 用于记录日志的log
    ngx_log_t          *log;

    // 连接的内存池
    // 默认大小是256字节
    ngx_pool_t         *pool;

    // socket的类型,SOCK_STREAM 表示TCP,
    int                 type;

    // 客户端的sockaddr
    struct sockaddr    *sockaddr;
    socklen_t           socklen;

    // 客户端的sockaddr,文本形式
    ngx_str_t           addr_text;

    ngx_proxy_protocol_t  *proxy_protocol;

    ...

    // 本地监听端口的socketaddr,也就是listening中的sockaddr
    // 有的时候local_sockaddr可能是0
    // 需要调用ngx_connection_local_sockaddr才能获得真正的服务器地址
    struct sockaddr    *local_sockaddr;
    socklen_t           local_socklen;

    // 接收客户端发送数据的缓冲区
    // 与listening中的rcvbuf不同,这个是nginx应用层的
    // 在ngx_http_wait_request_handler里分配内存
    ngx_buf_t          *buffer;

    // 侵入式队列,加入到ngx_cycle_t::reusable_connections_queue
    // 复用连接对象
    ngx_queue_t         queue;

    // 连接创建的计数器,可以用来标记不同的连接
    // ngx_event_accept.c:ngx_event_accept()
    // c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
    ngx_atomic_uint_t   number;

    // 1.19.10 连接开始的时间
    ngx_msec_t          start_time;

    // 处理的请求次数,在ngx_http_create_request里增加
    // 用来控制长连接里可处理的请求次数,指令keepalive_requests
    // 在stream框架里暂未使用
    ngx_uint_t          requests;

    // 标志位,表示连接有数据缓冲待发送
    // c->buffered |= NGX_HTTP_WRITE_BUFFERED;
    // 见ngx_http_write_filter_module.c
    unsigned            buffered:8;

    // 连接发生错误时记录日志的级别
    unsigned            log_error:3;     /* ngx_connection_log_error_e */

    // 1.12.0已经删除此字段
    //unsigned            unexpected_eof:1;

    // 是否已经超时
    unsigned            timedout:1;

    // 是否已经出错
    unsigned            error:1;

    // 是否tcp连接已经被销毁
    unsigned            destroyed:1;

    // 连接处于空闲状态
    unsigned            idle:1;

    // 连接可以复用,对应上面的queue成员
    // 即已经加入了ngx_cycle_t::reusable_connections_queue
    unsigned            reusable:1;

    // tcp连接已经关闭,可以回收复用
    // 手动置这个标志位可以强制关闭连接
    unsigned            close:1;

    unsigned            shared:1;

    // 正在发送文件
    unsigned            sendfile:1;

    // 是否已经设置发送数据时epoll的响应阈值
    // ngx_event.c:ngx_send_lowat()
    unsigned            sndlowat:1;

    unsigned            tcp_nodelay:2;   /* ngx_connection_tcp_nodelay_e */
    unsigned            tcp_nopush:2;    /* ngx_connection_tcp_nopush_e */

    unsigned            need_last_buf:1;

    ...

#if (NGX_THREADS || NGX_COMPAT)
    ngx_thread_task_t  *sendfile_task;
#endif
};
ngx_event_process_init(重点)

虽然已经删减了很多,但还是好长啊!

static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
    ngx_uint_t           m, i;
    ngx_event_t         *rev, *wev;
    ngx_listening_t     *ls;
    ngx_connection_t    *c, *next, *old;
    ngx_core_conf_t     *ccf;
    ngx_event_conf_t    *ecf;
    ngx_event_module_t  *module;

    // core模块的配置结构体
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

    // event_core模块的配置结构体
    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);

    // 使用master/worker多进程,使用负载均衡
    if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {

        // 设置全局变量
        // 使用负载均衡锁,刚开始未持有锁,设置抢锁的等待时间
        ngx_use_accept_mutex = 1;
        ngx_accept_mutex_held = 0;
        ngx_accept_mutex_delay = ecf->accept_mutex_delay;

    } else {
        // 单进程、未明确指定负载均衡,就不使用负载均衡
        ngx_use_accept_mutex = 0;
    }

    ...

    // 初始化两个延后处理的事件队列
    ngx_queue_init(&ngx_posted_accept_events);
    ngx_queue_init(&ngx_posted_next_events);
    ngx_queue_init(&ngx_posted_events);

    // 初始化定时器红黑树
    if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
        return NGX_ERROR;
    }

    // 遍历事件模块,但只执行实际使用的事件模块对应初始化函数
    for (m = 0; cycle->modules[m]; m++) {
        if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
            continue;
        }

        // 找到指令use指定的事件模型,或者是默认事件模型
        if (cycle->modules[m]->ctx_index != ecf->use) {
            continue;
        }

        // 找到事件模块

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

        // 调用事件模块的事件初始化函数
        // epoll调用ngx_epoll_init
        // 调用epoll_create初始化epoll机制
        // 参数size=cycle->connection_n / 2,但并无实际意义
        // 设置全局变量,操作系统提供的底层数据收发接口
        // 初始化全局的事件模块访问接口,指向epoll的函数
        // 默认使用et模式,边缘触发,高速
        if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
            /* fatal */
            exit(2);
        }

        // 找到一个事件模块即退出循环
        // 也就是说只能使用一种事件模型
        break;
    }

    // unix代码, 发送定时信号,更新时间用

    // NGX_USE_TIMER_EVENT标志量只有eventport/kqueue,epoll无此标志位
    // ngx_timer_resolution = ccf->timer_resolution;默认值是0
    // 所以只有使用了timer_resolution配置指令才会发信号
    if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
        struct sigaction  sa;
        struct itimerval  itv;

        // 注册SIGALARM信号处理器
        ngx_memzero(&sa, sizeof(struct sigaction));
        sa.sa_handler = ngx_timer_signal_handler;
        sigemptyset(&sa.sa_mask);

        if (sigaction(SIGALRM, &sa, NULL) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "sigaction(SIGALRM) failed");
            return NGX_ERROR;
        }

        // 设置信号发送的时间间隔,也就是nginx的时间精度
        // 收到信号后在信号处理器中设置ngx_event_timer_alarm标志变量
        // 在epoll的ngx_epoll_process_events里检查该标志,触发时间缓存的更新
        itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
        itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
        itv.it_value.tv_sec = ngx_timer_resolution / 1000;
        itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;

        //设置内核定时器
        if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "setitimer() failed");
        }
    }

    ...

    // 创建连接池数组,大小是cycle->connection_n
    // 直接使用malloc分配内存,没有使用内存池
    cycle->connections =
        ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
    if (cycle->connections == NULL) {
        return NGX_ERROR;
    }

    c = cycle->connections;

    // 创建读事件池数组,大小是cycle->connection_n
    // 由配置指令worker_connections指定
    cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
                                   cycle->log);
    if (cycle->read_events == NULL) {
        return NGX_ERROR;
    }

    // 读事件对象初始化
    rev = cycle->read_events;
    for (i = 0; i < cycle->connection_n; i++) {
        rev[i].closed = 1;
        rev[i].instance = 1;
    }

    // 创建写事件池数组,大小是 cycle->connection_n
    cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
                                    cycle->log);
    if (cycle->write_events == NULL) {
        return NGX_ERROR;
    }

    // 写事件对象初始化
    wev = cycle->write_events;
    for (i = 0; i < cycle->connection_n; i++) {
        wev[i].closed = 1;
    }

    // i是数组的末尾
    i = cycle->connection_n;
    next = NULL;

    // 把连接对象与读写事件关联起来
    // 注意i是数组的末尾,从后往前遍历
    do {
        i--;

        // 使用data成员,把连接对象串成链表
        // 刚开始所有连接对象都是空闲的
        c[i].data = next;

        // 读写事件
        c[i].read = &cycle->read_events[i];
        c[i].write = &cycle->write_events[i];

        // 连接的描述符是-1,表示无效
        c[i].fd = (ngx_socket_t) -1;

        // next指针指向数组的前一个元素
        next = &c[i];
    } while (i);

    // 连接对象已经串成链表,现在设置空闲链表指针
    // 此时next指向连接对象数组的第一个元素
    cycle->free_connections = next;

    // 目前连接没有使用,全是空闲连接
    cycle->free_connection_n = cycle->connection_n;

    /* for each listening socket */

    // 为每个监听端口分配一个连接对象
    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {

#if (NGX_HAVE_REUSEPORT)
        // 注意这里
        // 只有监听端口的worker id是本worker的才会监听
        // 也就是说虽然克隆了worker个监听端口,每个worker只会监听和自己worker id匹配的那一个
        if (ls[i].reuseport && ls[i].worker != ngx_worker) {
            continue;
        }
#endif

        // 获取一个空闲连接
        c = ngx_get_connection(ls[i].fd, cycle->log);

        if (c == NULL) {
            return NGX_ERROR;
        }

        c->type = ls[i].type; // 设置连接的类型,TCP或UDP
        c->log = &ls[i].log;

        // 连接的listening对象
        // 两者相互连接
        c->listening = &ls[i];
        ls[i].connection = c;

        // 监听端口只关心读事件
        rev = c->read;

        rev->log = c->log;

        // 设置accept标志,接受连接
        rev->accept = 1;

#if (NGX_HAVE_DEFERRED_ACCEPT)
        rev->deferred_accept = ls[i].deferred_accept;
#endif
        // 仅windows支持IOCP
        if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
            if (ls[i].previous) {

                /*
                 * delete the old accept events that were bound to
                 * the old cycle read events array
                 */

                old = ls[i].previous->connection;

                if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT)
                    == NGX_ERROR)
                {
                    return NGX_ERROR;
                }

                old->fd = (ngx_socket_t) -1;
            }
        }

        ...

        // 重要!!
        // 设置接收连接的回调函数为ngx_event_accept
        // 监听端口上收到连接请求时的回调函数,即事件handler
        // 从cycle的连接池里获取连接
        // 关键操作 ls->handler(c);调用其他模块的业务handler
        // 1.10使用ngx_event_recvmsg接收udp
        rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept    //tcp
                                                : ngx_event_recvmsg;  //udp

#if (NGX_HAVE_REUSEPORT)

        // 如果端口启用了reuseport,该端口不必使用accept_mutex管理
        // 直接添加到epoll中开始监听reuseport端口,LT模式,防止连接丢失
        // ngx_epoll_add_event()
        if (ls[i].reuseport) {
            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
                return NGX_ERROR;
            }

            continue;
        }

#endif

        // 如果使用负载均衡锁,不向epoll添加事件,等到抢到锁再添加
        if (ngx_use_accept_mutex) {
            continue;
        }

        // 走到这里,说明不使用负载均衡锁
#if (NGX_HAVE_EPOLLEXCLUSIVE)
        // 支持 EPOLLEXCLUSIVE 标志(Linux 4.5+ 引入)

        // 如果使用epoll且不是单Worker
        if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
            && ccf->worker_processes > 1)
        {
            // nginx 1.9.x不再使用rtsig

            // 没有使用reuseport,且不使用负载均衡锁,如果系统支持EPOLLEXCLUSIVE标志,
            // 使用该标志避免“惊群”
            // 监听端口添加EPOLLEXCLUSIVE标志位,加上默认的LT模式(防止连接丢失)
            // 加入到epoll中,开始监听
            // ngx_epoll_add_event()
            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT)
                == NGX_ERROR)
            {
                return NGX_ERROR;
            }

            // 跳过下面的普通add event
            continue;
        }

#endif

        // 端口没有使用reuseport,不使用负载均衡锁,且系统不支持EPOLLEXCLUSIVE标志
        // 加入epoll事件,开始监听,可能会发生“惊群”
        if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
            return NGX_ERROR;
        }


    } // 为每个监听端口分配一个连接对象循环结束

    return NGX_OK;
}

该方法主要做了下面几件事:

  1. 打开 accept_mutex 负载均衡锁,用于防止“惊群”。

Nginx 使用 accept_mutex 负载均衡锁,确保同一时间只有一个 Worker 将监听 Socket 加入 epoll。抢占锁成功的 Worker 处理新连接,其他 Worker 继续处理已有连接的事件。缺点是锁竞争可能导致负载不均(某些 Worker 长期抢到锁)而且单 Worker accept 限制了多核并行的能力。(性能最差)

Linux 3.9+ 内核开始支持 SO_REUSEPORT 监听端口复用。Nginx 从 1.9.1 版本开始支持,配置 reuseport 参数后,每个 Worker 拥有独立监听 Socket(它们都绑定同一端口),内核根据四元组哈希均匀分配新连接给多个监听 Socket 的连接队列,内核级负载均衡,多队列并行 accept。(性能最高)

accept_mutex 目前只在不支持 SO_REUSEPORT 的老版本 Linux 上使用。

Linux 4.5+ 内核引入了 EPOLLEXCLUSIVE 标志,该标志确保事件发生时仅唤醒一个 epoll_wait 的进程/线程。多个 Worker 仍需使用相同的监听 socket,因此也共享相同的 Socket 连接队列,单 Worker aceept 无法完全发挥多核的能力。(性能中等)

  1. 初始化两个队列,一个用于存放不能及时处理的建立连接事件,一个用于存储不能及时处理的读写事件。
  2. 初始化定时器,该定时器就是一颗红黑树,根据时间对事件进行排序。
  3. (可选)注册 SIGALARM 信号处理器(用于更新时间缓存)。
  4. (可选)调用 setitimer 创建内核定时器,周期性向进程发送一个 SIGALRM 信号,中断 epoll_wait 的调用,从而可以及时更新时间缓存。
  5. 调用使用的 ngx_epoll_module 的 ctx 的 actions 的 init 方法,即 **ngx_epoll_init** 函数(后面介绍)。该函数较为简单,主要的作用是调用 epoll_create() 和创建用于存储 epoll_wait() 返回事件的数组 event_list。
  6. 如果再配置中设置了 timer_resolution,则要设置控制时间精度,用于控制 Nginx 时间。后面重点讲解。
  7. 分配连接池空间、读事件结构体数组、写事件结构体数组。

每一个 ngx_connection_t 结构体都有两个 ngx_event_t 结构体,一个读事件,一个写事件。在这个阶段,会向内存池中申请 3 个数组:cycle->connectionscycle->read_eventscycle->write_events,某个连接和它的读写事件在这三个数组中的索引是相同的。令 cycle->free_connections 指向第一个未使用的 ngx_connection_t 结构体。

  1. 为每个监听端口分配连接

在此阶段,会获取 cycle->listening 数组中的 ngx_listening_t 结构体元素。前面,我们已经打开了 Nginx 需要监听的所有端口。在这里,会遍历每一个打开的监听端口,并为它们分配 ngx_connection_t 对象。

reuseport 端口虽然克隆了worker 数量个监听端口,但每个worker只会给和自己 worker id 匹配的那一个监听端口分配连接对象。

  1. 为每个监听端口的读事件建立处理器 handler

这里为连接的读事件设置回调方法 handler。这里的 handler 是 ngx_event_accept 函数,用来接收连接,将在后文讲解。

监听端口不需要处理写事件,它只是用来监听客户端的连接(读事件),并 accept 连接。

  1. 将每个监听端口读事件添加到 epoll 中,开始监听。

使用 accept_mutex 锁的情况,不在这里添加,等 Worker 抢到了锁才添加。

在此处,会调用 ngx_epoll_module 的 actions 的 ngx_epoll_add_event 函数,将监听端口的读事件添加到 epoll 中。

至此,ngx_event_process_init 的工作完成,事件模块的初始化也完成了。后面 Worker 开始进入循环监听阶段。

ngx_epoll_init

ngx_epoll_module事件模块的实现了函数指针表中的 init 方法,即 **ngx_epoll_init** 函数。该函数较为简单,主要做了下面这些事:

  • 调用 epoll_create() (创建独属于该 Worker 的 epoll 实例);
  • 创建用于存储 epoll_wait() 返回事件的数组 event_list
  • 设置全局变量 nevents 为数组 event_list 的长度;
  • 设置全局变量 ngx_iongx_linux_iongx_linux_io 是 Linux 操作系统的数据收发接口。
  • 设置全局事件处理函数表为 ngx_epoll_module_ctx.actions。不同事件模型处理事件的接口不相同。
  • 设置全局事件标志 ngx_event_flagsNGX_USE_CLEAR_EVENT|NGX_USE_GREEDY_EVENT|NGX_USE_EPOLL_EVENT
static ngx_int_t
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
    ngx_epoll_conf_t  *epcf;

    // 获取epoll模块的配置
    epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);

    // ep == -1表示还没有创建epoll句柄,需要初始化
    if (ep == -1) {
        // 创建epoll句柄
        // 参数size=cycle->connection_n / 2,但并无实际意义
        ep = epoll_create(cycle->connection_n / 2);

        // epoll初始化失败
        if (ep == -1) {
            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
                          "epoll_create() failed");
            return NGX_ERROR;
        }

#if (NGX_HAVE_EVENTFD)
        // 初始化多线程通知用的描述符,给它分配事件、连接对象,并添加到epoll中
        if (ngx_epoll_notify_init(cycle->log) != NGX_OK) {

            // 如果初始化失败,那么notify指针置空
            ngx_epoll_module_ctx.actions.notify = NULL;
        }
#endif

// aio暂不研究
#if (NGX_HAVE_FILE_AIO)
        ngx_epoll_aio_init(cycle, epcf);
#endif

#if (NGX_HAVE_EPOLLRDHUP)
        ngx_epoll_test_rdhup(cycle);
#endif
    }

    // 配置指令epoll_events指定事件数组的大小
    // 检查当前事件数组的大小,最开始nevents是0
    if (nevents < epcf->events) {

        // 如果是reload,那么就先释放,再重新分配内存
        if (event_list) {
            ngx_free(event_list);
        }

        // 相当于vector.resize(cf.events)
        event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
                               cycle->log);
        if (event_list == NULL) {
            return NGX_ERROR;
        }
    }

    // 设置正确的数组长度
    nevents = epcf->events;

    // 设置全局变量,操作系统提供的底层数据收发接口
    // ngx_posix_init.c里初始化为linux的底层接口
    ngx_io = ngx_os_io;

    // 初始化全局的事件模块访问接口,指向epoll的函数
    ngx_event_actions = ngx_epoll_module_ctx.actions;

#if (NGX_HAVE_CLEAR_EVENT)
    // 默认使用ET模式,边缘触发,高速
    ngx_event_flags = NGX_USE_CLEAR_EVENT
#else
    ngx_event_flags = NGX_USE_LEVEL_EVENT
#endif
                      |NGX_USE_GREEDY_EVENT
                      |NGX_USE_EPOLL_EVENT;

    return NGX_OK;
}
ngx_epoll_add_event

向 epoll 添加单个事件

// 向epoll中添加单个监听事件
// 参数event通常是NGX_READ_EVENT或NGX_WRITE_EVENT
// 参数flags则通常是水平触发NGX_LEVEL_EVENT或边缘触发NGX_CLEAR_EVENT
// 检查事件对象的active标志,决定是新添加还是修改
// 避免丢失正在监听的读/写事件
// flags传0,可认为是 NGX_LEVEL_EVENT,即水平触发
static ngx_int_t
ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{
    int                  op;
    uint32_t             events, prev;
    ngx_event_t         *e;
    ngx_connection_t    *c;
    struct epoll_event   ee;

    // 获取事件关联的连接对象
    c = ev->data;

    // 计算epoll的标志位
    events = (uint32_t) event;

    // 监听读事件之前需要保存之前监听的写事件(如果有的话),不然写事件的监听会丢失掉
    if (event == NGX_READ_EVENT) {
        e = c->write;
        prev = EPOLLOUT; // 写事件监听的标志

        // 读事件的epoll标志
#if (NGX_READ_EVENT != EPOLLIN|EPOLLRDHUP)
        events = EPOLLIN|EPOLLRDHUP;
#endif

    // 监听写事件之前需要保存之前监听的读事件(如果有的话),不然读事件的监听会丢失
    } else {
        e = c->read;
        prev = EPOLLIN|EPOLLRDHUP; // 读事件监听的标志

        // 写事件的epoll标志
#if (NGX_WRITE_EVENT != EPOLLOUT)
        events = EPOLLOUT;
#endif
    }

    // 准备监听读事件时判断是否有正在监听写事件(active=1),如果是,在监听读事件的同时追加上对写事件的监听,
    // 反之,准备监听写事件时判断是否有正在监听读事件,如果是,在监听写事件的同时追加上对读事件的监听。
    // 当读/写事件添加到epoll监听后,active被设置。
    // 之前有监听的事件就使用修改EPOLL_CTL_MOD加入新的监听事件,
    // 之前没有监听的事件就使用EPOLL_CTL_ADD直接加入监听事件。
    if (e->active) {
        op = EPOLL_CTL_MOD;
        events |= prev;

    } else {
        op = EPOLL_CTL_ADD;
    }

#if (NGX_HAVE_EPOLLEXCLUSIVE && NGX_HAVE_EPOLLRDHUP)
    if (flags & NGX_EXCLUSIVE_EVENT) {
        events &= ~EPOLLRDHUP;
    }
#endif

    // 加上传递的flags标志,里面可能有ET
    ee.events = events | (uint32_t) flags;

    // union的指针成员,关联到连接对象
    // 因为目前的32位/64位的计算机指针地址低位都是0(字节对齐)
    // 所以用最低位来存储instance标志,即一个bool值
    // 在真正访问连接对象时需要把低位的信息去掉
    ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);

    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
                   "epoll add event: fd:%d op:%d ev:%08XD",
                   c->fd, op, ee.events);

    // 到这里,已经确定了是新添加还是修改epoll事件
    // 执行系统调用,添加epoll关注事件
    if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
                      "epoll_ctl(%d, %d) failed", op, c->fd);
        return NGX_ERROR;
    }

    // 添加事件监听成功,设置active标志
    ev->active = 1;
#if 0
    ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
#endif

    return NGX_OK;
}

3. 事件处理

前面已经完成了所有事件相关的初始化。接下来就该 Worker 循环处理网络事件了。相关代码位于主循环函数 ngx_worker_process_cycle 中:

static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    ...
        
    ngx_worker_process_init(cycle, worker);

    ...
        
    // 无限循环,处理事件和信号
    for ( ;; ) {

        ...		// 省略信号处理 

        // 处理事件的核心函数, event模块里
        // 处理socket读写事件和定时器事件
        // 获取负载均衡锁,监听端口接受连接
        // 调用epoll模块的ngx_epoll_process_events
        // 然后处理超时事件和在延后队列里的所有事件
        // nginx大部分的工作量都在这里
        ngx_process_events_and_timers(cycle);

        ...		// 省略信号处理 

    } // 无限循环,处理事件和信号
}

该函数中有个无限 for(;;) 循环在不断处理信号和网络事件。网络和定时器事件主要调用 ngx_process_events_and_timers

3.1 Worker 事件处理

ngx_process_events_and_timers

ngx_process_events_and_timers 函数是 Nginx 处理事件的核心函数,代码如下:

void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    ngx_uint_t  flags;
    ngx_msec_t  timer, delta;

    // 指令timer_resolution指定时间精度,默认为0
    // 在ngx_event_module_init中设置全局变量ngx_timer_resolution的值
    // nginx更新时间缓存的频率,如果设置了会定时发送sigalarm信号更新时间
    // ngx_timer_resolution = ccf->timer_resolution,默认值是0
    if (ngx_timer_resolution) {
        // epoll_wait无限等待,直到发生事件,或者被sigalarm信号中断
        timer = NGX_TIMER_INFINITE;
        flags = 0;

    } else {
        // 没有设置时间精度,默认设置
        // 在定时器红黑树里找到最小的时间,二叉树查找很快
        // timer > 0,红黑树里即将超时的事件的时间
        // timer < 0,表示红黑树为空,即无超时事件,返回 NGX_TIMER_INFINITE == -1
        // timer == 0,意味着在红黑树里已经有事件超时了,必须立即处理。
        // timer == 0,epoll_wait不会等待,有事件收集完事件立即返回。没事件也立即返回。
        timer = ngx_event_find_timer();

        // NGX_UPDATE_TIME要求epoll阻塞等待这个时间,
        // 然后不管有没有发生事件,返回更新时间缓存
        flags = NGX_UPDATE_TIME;

        // nginx 1.9.x不再使用old threads代码
    }

    // 现在已经设置了合适的timer和flag

    // 负载均衡锁标志量, accept_mutex on
    // 1.9.x,如果使用了reuseport,那么ngx_use_accept_mutex==0
    //
    // 1.11.3开始,默认不使用负载均衡锁,提高性能,下面的代码直接跳过
    if (ngx_use_accept_mutex) {
        // ngx_accept_disabled = ngx_cycle->connection_n / 8
        //                      - ngx_cycle->free_connection_n;
        // ngx_accept_disabled是总连接数的1/8-空闲连接数
        // 也就是说空闲连接数小于总数的1/8,那么就暂时停止接受连接,负载均衡
        if (ngx_accept_disabled > 0) {

            // 但也不能永远不接受连接,毕竟还是有空闲连接的,所以每次要减一
            ngx_accept_disabled--;

        } else {
            // 尝试获取负载均衡锁,开始监听端口
            // 如未获取则不监听端口
            // 内部调用ngx_enable_accept_events/ngx_disable_accept_events
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                // 如果监听失败,那么直接结束函数,不处理epoll事件
                return;
            }

            // ngx_trylock_accept_mutex执行成功
            // 使用变量ngx_accept_mutex_held检查是否成功获取了锁

            // 此时已经获得了锁,接下来epoll_wait返回的事件需要加入延后队列,然后释放锁。
            // 如果等处理完所有事件再释放锁,可能事件处理时间过长,迟迟无法释放锁,
            // 这样其他worker即使无事可做也无法抢到锁,accept新连接,
            // 新连接占满连接队列,导致大量客户端连接请求被拒绝。
            if (ngx_accept_mutex_held) {

                // 加上NGX_POST_EVENTS标志
                // epoll获得的所有事件都会加入到ngx_posted_events
                // 待释放锁后再逐个处理,尽量避免过长时间持有锁
                flags |= NGX_POST_EVENTS;

            } else {
                // 未获取到锁
                // 要求epoll无限等待,或者等待时间超过配置的ngx_accept_mutex_delay
                // 也就是说nginx的epoll不会等待超过ngx_accept_mutex_delay的500毫秒
                // 如果epoll有事件发生,那么此等待时间无意义,epoll_wait会立即返回
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    // epoll的超时时间最大就是ngx_accept_mutex_delay
                    // ngx_accept_mutex_delay = ecf->accept_mutex_delay;
                    // 如果时间精度设置的太粗,那么就使用这个时间,500毫秒
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }   //ngx_use_accept_mutex

    // 如果不使用负载均衡,或者没有抢到锁
    // 那么就不会使用延后处理队列,即没有NGX_POST_EVENTS标志

    // 1.11.3开始,默认不使用负载均衡锁,提高性能
    // 省去了锁操作和队列操作

    // 不管是否获得了负载均衡锁,都要处理事件和定时器
    // 如果获得了负载均衡锁,事件就会多出一个accept事件
    // 否则只有普通的读写事件和定时器事件

    // 1.17.5新增,处理ngx_posted_next_events
    if (!ngx_queue_empty(&ngx_posted_next_events)) {
        ngx_event_move_posted_next(cycle);
        timer = 0;
    }

    // 获取当前的时间,毫秒数
    delta = ngx_current_msec;

    // #define ngx_process_events   ngx_event_actions.process_events
    // 实际上就是ngx_epoll_process_events
    //
    // epoll模块核心功能,调用epoll_wait处理发生的事件
    // 使用event_list和nevents获取内核返回的事件
    // timer是无事件发生时最多等待的时间,即超时时间
    // 如果ngx_event_find_timer返回timer==0,那么epoll不会等待,立即返回
    // 函数可以分为两部分,一是用epoll获得事件,二是处理事件,加入延后队列
    //
    // 如果不使用负载均衡(accept_mutex off)
    // 那么所有IO事件均在此函数里处理,即搜集事件并调用handler
    (void) ngx_process_events(cycle, timer, flags);

    // 在ngx_process_events里缓存的时间肯定已经更新
    // 计算得到epoll一次调用消耗的毫秒数
    delta = ngx_current_msec - delta;

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "timer delta: %M", delta);

    // 先处理连接事件,通常只有一个accept的连接
    // in ngx_event_posted.c
    // 实际上调用的就是ngx_event_accept
    // 在http模块里是http.c:ngx_http_init_connection
    //
    // 如果不使用负载均衡锁(accept_mutex off)或者使用reuseport
    // 那么此处就是空操作,因为队列为空
    ngx_event_process_posted(cycle, &ngx_posted_accept_events);

    // 释放锁,其他进程可以获取,再监听端口
    // 这里只处理accept事件,工作量小,可以尽快释放锁,供其他进程使用
    if (ngx_accept_mutex_held) {
        // 释放负载均衡锁
        // 其他进程最多等待ngx_accept_mutex_delay毫秒后
        // 再走ngx_trylock_accept_mutex决定端口的监听权
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }

    // 1.19.9
    // 如果消耗了一点时间,那么看看是否定时器里有过期的
    //if (delta) {
    //    ngx_event_expire_timers();
    //}

    // 遍历定时器红黑树,找出所有过期的事件,调用handler处理超时
    // 其中可能有的socket读写超时,那么就结束请求,断开连接
    ngx_event_expire_timers();

    // 接下来处理延后队列里的事件,即调用事件的handler(ev),收发数据
    // in ngx_event_posted.c
    // 这里因为要处理大量的事件,而且是简单的顺序调用,所以可能会阻塞
    // nginx大部分的工作量都在这里
    // 注意与accept的函数是相同的,但队列不同,即里面的事件不同
    //
    // 如果不使用负载均衡锁(accept_mutex off)或者使用reuseport
    // 那么此处就是空操作,因为队列为空
    ngx_event_process_posted(cycle, &ngx_posted_events);
}

主要的工作可以分为下面几部分:

  • 设置 nginx 更新时间的方式。

Nginx 会缓存时间,每隔一段时间调用 ngx_time_update 函数更新时间缓存。那么多久更新一次呢?Nginx 提供两种方式:

1. `timer_resolution` 模式。在 Nginx 配置文件中,可以使用 `timer_resolution` 指令来选择此方式。如果使用此方式,会将`epoll_wait` 的阻塞时间(timout=-1)设置为无穷大,即一直阻塞。由前文知道,在 Worker 初始化时,调用 `ngx_event_process_init` 函数设置一个定时器和建立一个 `SIGALARM` 信号处理函数,其中定时器会每隔 `timer_resolution` 的时间发送一个 `SIGALRM` 信号,而当 Worker 收到时间定时器发送的信号,会将 `epoll_wait` 函数中断,同时 `SIGALRM` 信号的中断处理函数,会将全局变量 `ngx_event_timer_alarm` 置为 1。epoll_wait 返回后会检查该变量,从而调用 `ngx_time_update` 函数来更新 Nginx 的时间。
2. 如果没在配置文件中配置 `timer_resolution` 指令,Nginx 默认会根据 `ngx_event_find_timer` 的返回值来设置 `epoll_wait` 的阻塞时间(可能还需考虑 `accept_mutex_delay` 因素),`ngx_event_find_timer` 函数返回的是下一个时间事件发生的时间与当前时间的差值,即让 `epoll_wait` 阻塞到下一个时间事件发生为止。当使用这种模式,每当 `epoll_wait` 返回,都会调用 `ngx_time_update` 函数更新时间。
  • 使用负载均衡锁。抢锁成功后才有权监听端口,接收连接。加锁成功会设置标志 NGX_POST_EVENTS,锁不释放,直到 epoll_wait 调用返回,判断是否有 NGX_POST_EVENTS 标志,将 accept 事件和读写事件加入延后队列后,这时才释放锁。

如果一个 Worker 接收的连接数过多,超过了最大连接数的八分之一。则不会抢锁。

  • 调用事件处理函数 ngx_process_events,epoll 使用的是 ngx_epoll_process_events 函数。
  • 统计 ngx_process_events 函数的调用耗时。
  • 优先处理 ngx_posted_accept_events 队列的连接事件。这里就是遍历 ngx_posted_accept_events 队列,依次调用事件的 handler 方法,这里 accept 事件的 handler 为 ngx_event_accept
  • 释放负载均衡锁。注意:必须得处理完 ngx_posted_accept_events 队列中的连接事件才能释放锁。否则,其它 Worker 争抢到锁后,监听端口,也可能会触发 accept 事件。导致多个 Worker 进程同时 accept 连接。
  • 处理定时器事件,具体操作是在定时器红黑树中查找过期的事件,调用其 handler 方法。
  • 处理 ngx_posted_events 队列的读写事件,即遍历 ngx_posted_events 队列,依次调用读写事件的 handler 方法。
ngx_epoll_process_events
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
    int                events;
    uint32_t           revents;
    ngx_int_t          instance, i;
    ngx_uint_t         level;
    ngx_err_t          err;
    ngx_event_t       *rev, *wev;
    ngx_queue_t       *queue;
    ngx_connection_t  *c;

    /* NGX_TIMER_INFINITE == INFTIM */

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "epoll timer: %M", timer);

    // 如果使用负载均衡且抢到了accept锁,那么flags里有NGX_POST_EVENTS标志
    // 如果没有设置更新缓存时间的精度,那么flags里有NGX_UPDATE_TIME

    // 调用epoll_wait处理发生的事件
    // 使用event_list和nevents获取内核返回的事件
    // 返回值events是实际获得的事件数量
    // epoll_wait等待最多timer时间后返回
    // 如果epoll有事件发生,那么等待时间timer无意义,epoll_wait立即返回
    // 如果ngx_event_find_timer返回timer==0,那么epoll不会等待,立即返回
    events = epoll_wait(ep, event_list, (int) nevents, timer);

    // 检查是否发生了错误
    // 如果调用epoll_wait获得了0个或多个事件,就没有错误
    err = (events == -1) ? ngx_errno : 0;

    // 如果要求更新时间,或者收到了更新时间的信号
    // 通常event模块调用时总会传递NGX_UPDATE_TIME,这时就会更新缓存的时间
    // sigalarm信号的处理函数设置ngx_event_timer_alarm变量
    if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
        // in ngx_times.c,系统调用,更新时间缓存
        ngx_time_update();
    }

    // 错误处理
    if (err) {

        // 错误是由信号中断引起的
        if (err == NGX_EINTR) {

            // 如果是更新时间的信号,那么就不是错误
            if (ngx_event_timer_alarm) {
                ngx_event_timer_alarm = 0;
                return NGX_OK;
            }

            level = NGX_LOG_INFO;

        } else {
            level = NGX_LOG_ALERT;
        }

        ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
        return NGX_ERROR;
    }

    // 0个事件,说明nginx没有收到任何请求或者数据收发
    if (events == 0) {
        // #define NGX_TIMER_INFINITE  (ngx_msec_t) -1
        // 不是无限等待,在很短的时间里无事件发生,是正常现象
        if (timer != NGX_TIMER_INFINITE) {
            return NGX_OK;
        }

        // 无限等待,却没有任何事件, 出错了
        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                      "epoll_wait() returned no events without timeout");
        return NGX_ERROR;
    }

    // 调用epoll_wait获得了多个事件,存储在event_list里,共events个
    // 遍历event_list数组,逐个处理事件
    for (i = 0; i < events; i++) {

        // 从epoll结构体的union.ptr获得连接对象指针
        c = event_list[i].data.ptr;

        // 因为目前的32位/64位的计算机指针地址低位都是0(字节对齐)
        // 所以用最低位来存储instance标志,即一个bool值
        // 在真正取出连接对象时需要把低位的信息去掉
        instance = (uintptr_t) c & 1;

        // 此时才是真正的连接对象指针
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);

        // 优先查看连接里的读事件
        rev = c->read;

        // fd == -1描述符无效
        // instance不相等说明事件已经过期,旧连接被复用了,
        // 直接跳过
        if (c->fd == -1 || rev->instance != instance) {

            /*
             * the stale event from a file descriptor
             * that was just closed in this iteration
             */

            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "epoll: stale event %p", c);
            continue;
        }

        // 获取epoll的事件标志
        revents = event_list[i].events;

        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "epoll: fd:%d ev:%04XD d:%p",
                       c->fd, revents, event_list[i].data.ptr);

        // EPOLLERR|EPOLLHUP是发生了错误
        // 将其伪装成就绪的读写事件,后续在处理读写事件时,会调用对应的读写接口,从而报错,
        // 触发连接关闭。
        if (revents & (EPOLLERR|EPOLLHUP)) {
            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "epoll_wait() error on fd:%d ev:%04XD",
                           c->fd, revents);

            /*
             * if the error events were returned, add EPOLLIN and EPOLLOUT
             * to handle the events at least in one active handler
             */

            revents |= EPOLLIN|EPOLLOUT;
        }

        // 发生了错误,但没有EPOLLIN|EPOLLOUT的读写事件
        //if ((revents & (EPOLLERR|EPOLLHUP))
        //     && (revents & (EPOLLIN|EPOLLOUT)) == 0)
        //{
        //    /*
        //     * if the error events were returned without EPOLLIN or EPOLLOUT,
        //     * then add these flags to handle the events at least in one
        //     * active handler
        //     */

        //    // 加上一个读写事件,保证后续有handler可以处理
        //    // 实际上会由读事件来处理
        //    revents |= EPOLLIN|EPOLLOUT;
        //}

        // 有读事件,且读事件是可用的
        if ((revents & EPOLLIN) && rev->active) {

#if (NGX_HAVE_EPOLLRDHUP)
            if (revents & EPOLLRDHUP) {
                rev->pending_eof = 1;
            }
#endif

            // 读事件可用
            rev->ready = 1;

            // nginx 1.17.5新增,用在ngx_recv时检查
            rev->available = -1;

            // 检查此事件是否要延后处理
            // 如果使用负载均衡且抢到accept锁,那么flags里有NGX_POST_EVENTS标志
            // 1.9.x使用reuseport,那么就不延后处理
            if (flags & NGX_POST_EVENTS) {
                // 是否是接受请求的事件,两个延后处理队列
                queue = rev->accept ? &ngx_posted_accept_events
                                    : &ngx_posted_events;

                // 暂不处理,而是加入延后处理队列
                // 加快事件的处理速度,避免其他进程的等待
                // in ngx_event_posted.h,函数宏
                ngx_post_event(rev, queue);

            } else {
                // 不accept的进程不需要入队,直接处理
                // 不延后,立即调用读事件的handler回调函数处理事件
                // 1.9.x reuseport直接处理,省去了入队列出队列的成本,更快
                rev->handler(rev);
            }
        }

        // 读事件处理完后再查看连接里的写事件
        wev = c->write;

        // 有写事件,且写事件是可用的
        if ((revents & EPOLLOUT) && wev->active) {

            // fd == -1描述符无效
            // instance不对,连接有错误
            if (c->fd == -1 || wev->instance != instance) {

                /*
                 * the stale event from a file descriptor
                 * that was just closed in this iteration
                 */

                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                               "epoll: stale event %p", c);
                continue;
            }

            // 写事件可用
            wev->ready = 1;

            // 1.10新增,使用complete标记多线程异步操作已经完成
#if (NGX_THREADS)
            wev->complete = 1;
#endif

            // 检查此事件是否要延后处理
            // 1.9.x使用reuseport,那么就不延后处理
            if (flags & NGX_POST_EVENTS) {
                // 暂不处理,而是加入延后处理队列
                // 加快事件的处理速度,避免其他进程的等待
                // 写事件只有一个队列
                // in ngx_event_posted.h,函数宏
                ngx_post_event(wev, &ngx_posted_events);

            } else {
                // 不accept的进程不需要入队,直接处理
                // 不延后,立即调用写事件的handler回调函数处理事件
                // 1.9.x reuseport直接处理,省去了入队列出队列的成本,更快
                wev->handler(wev);
            }
        }
    }       //for循环结束,处理完epoll_wait获得的内核事件

    return NGX_OK;
}
ngx_event_accept
// 仅接受tcp连接
// ngx_event_process_init里设置接受连接的回调函数为ngx_event_accept,可以接受连接
// 监听端口上收到连接请求时的回调函数,即事件handler
// 从cycle的连接池里获取连接
// 关键操作 ls->handler(c);调用其他模块的业务handler
void
ngx_event_accept(ngx_event_t *ev)
{
    socklen_t          socklen;
    ngx_err_t          err;
    ngx_log_t         *log;
    ngx_uint_t         level;
    ngx_socket_t       s;
    ngx_event_t       *rev, *wev;
    ngx_sockaddr_t     sa;
    ngx_listening_t   *ls;
    ngx_connection_t  *c, *lc;
    ngx_event_conf_t  *ecf;
#if (NGX_HAVE_ACCEPT4)
    static ngx_uint_t  use_accept4 = 1;
#endif

    // 事件已经超时
    if (ev->timedout) {
        // 遍历监听端口列表,重新加入epoll连接事件
        if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) {
            return;
        }

        // 保证监听不超时
        ev->timedout = 0;
    }

    // 得到event core模块的配置,检查是否接受多个连接
    ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);

    // rtsig在nginx 1.9.x已经删除
    if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) {
        // epoll是否允许尽可能接受多个请求
        // 这里的ev->available仅使用1个bit的内存空间
        ev->available = ecf->multi_accept;
    }

    // 事件的连接对象
    lc = ev->data;

    // 事件对应的监听端口对象
    // 这里是一个关键,连接->事件->监听端口
    // 决定了要进入stream还是http子系统
    ls = lc->listening;

    // 此时还没有数据可读
    ev->ready = 0;

    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
                   "accept on %V, ready: %d", &ls->addr_text, ev->available);

    do {
        socklen = sizeof(ngx_sockaddr_t);

        // 调用accept接受连接,返回socket对象
        // accept4是linux的扩展功能,可以直接把连接的socket设置为非阻塞
#if (NGX_HAVE_ACCEPT4)
        if (use_accept4) {
            s = accept4(lc->fd, &sa.sockaddr, &socklen, SOCK_NONBLOCK);
        } else {
            s = accept(lc->fd, &sa.sockaddr, &socklen);
        }
#else
        s = accept(lc->fd, &sa.sockaddr, &socklen);
#endif

        // 接受连接出错
        if (s == (ngx_socket_t) -1) {
            err = ngx_socket_errno;

            // EAGAIN,此时已经没有新的连接,用于multi_accept
            if (err == NGX_EAGAIN) {
                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err,
                               "accept() not ready");
                return;
            }

            level = NGX_LOG_ALERT;

            if (err == NGX_ECONNABORTED) {
                level = NGX_LOG_ERR;

            } else if (err == NGX_EMFILE || err == NGX_ENFILE) {
                level = NGX_LOG_CRIT;
            }

#if (NGX_HAVE_ACCEPT4)
            ngx_log_error(level, ev->log, err,
                          use_accept4 ? "accept4() failed" : "accept() failed");

            if (use_accept4 && err == NGX_ENOSYS) {
                use_accept4 = 0;
                ngx_inherited_nonblocking = 0;
                continue;
            }
#else
            ngx_log_error(level, ev->log, err, "accept() failed");
#endif

            if (err == NGX_ECONNABORTED) {
                if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
                    ev->available--;
                }

                if (ev->available) {
                    continue;
                }
            }

            // 系统的文件句柄数用完了
            if (err == NGX_EMFILE || err == NGX_ENFILE) {
                // 遍历监听端口列表,删除epoll监听连接事件,不接受请求
                if (ngx_disable_accept_events((ngx_cycle_t *) ngx_cycle, 1)
                    != NGX_OK)
                {
                    return;
                }

                // 解锁负载均衡,允许其他进程接受请求
                if (ngx_use_accept_mutex) {
                    if (ngx_accept_mutex_held) {
                        ngx_shmtx_unlock(&ngx_accept_mutex);
                        ngx_accept_mutex_held = 0;
                    }

                    //未持有锁,暂时不接受请求
                    ngx_accept_disabled = 1;

                } else {
                    // 不使用负载均衡
                    // 等待一下,再次尝试接受请求
                    ngx_add_timer(ev, ecf->accept_mutex_delay);
                }
            }

            return;
        } // 接受连接出错

#if (NGX_STAT_STUB)
        (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1);
#endif

        // 此时accept返回了一个socket描述符s

        // ngx_accept_disabled是总连接数的1/8-空闲连接数
        // 也就是说空闲连接数小于总数的1/8,那么就暂时停止接受连接
        ngx_accept_disabled = ngx_cycle->connection_n / 8
                              - ngx_cycle->free_connection_n;

        // 从全局变量ngx_cycle里获取空闲链接,即free_connections链表
        c = ngx_get_connection(s, ev->log);

        // 如果没有空闲连接,那么关闭socket,无法处理请求
        if (c == NULL) {
            if (ngx_close_socket(s) == -1) {
                ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
                              ngx_close_socket_n " failed");
            }

            return;
        }

        // 1.10连接对象里新的字段,表示连接类型是tcp
        c->type = SOCK_STREAM;

#if (NGX_STAT_STUB)
        (void) ngx_atomic_fetch_add(ngx_stat_active, 1);
#endif

        // 创建连接使用的内存池
        // stream模块设置连接的内存池是256bytes,不可配置
        // http模块可以在ngx_http_core_srv_conf_t里配置
        c->pool = ngx_create_pool(ls->pool_size, ev->log);
        if (c->pool == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }

        if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) {
            socklen = sizeof(ngx_sockaddr_t);
        }

        // 拷贝客户端sockaddr
        c->sockaddr = ngx_palloc(c->pool, socklen);
        if (c->sockaddr == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }

        ngx_memcpy(c->sockaddr, &sa, socklen);

        // 连接使用一个新的日志对象
        log = ngx_palloc(c->pool, sizeof(ngx_log_t));
        if (log == NULL) {
            ngx_close_accepted_connection(c);
            return;
        }

        /* set a blocking mode for iocp and non-blocking mode for others */

        // 设置socket为非阻塞
        // ngx_inherited_nonblocking => os/unix/ngx_posix_init.c
        // 如果linux支持accept4,那么ngx_inherited_nonblocking = true
        if (ngx_inherited_nonblocking) {

            // NGX_USE_IOCP_EVENT是win32的标志
            // 这段代码在linux里不会执行,也就是说无动作
            // 因为accept4已经设置了socket非阻塞
            if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
                if (ngx_blocking(s) == -1) {
                    ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
                                  ngx_blocking_n " failed");
                    ngx_close_accepted_connection(c);
                    return;
                }
            }

        } else {
            // 如果linux不支持accept4,需要设置为非阻塞
            if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
                if (ngx_nonblocking(s) == -1) {
                    ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_socket_errno,
                                  ngx_nonblocking_n " failed");
                    ngx_close_accepted_connection(c);
                    return;
                }
            }
        }

        // 从监听端口拷贝
        *log = ls->log;

        // 连接的收发数据函数
        // #define ngx_recv             ngx_io.recv
        // #define ngx_recv_chain       ngx_io.recv_chain
        // ngx_posix_init.c里初始化为linux的底层接口
        c->recv = ngx_recv;
        c->send = ngx_send;
        c->recv_chain = ngx_recv_chain;
        c->send_chain = ngx_send_chain;

        c->log = log;
        c->pool->log = log;

        // 设置其他的成员
        c->socklen = socklen;
        c->listening = ls;
        c->local_sockaddr = ls->sockaddr;
        c->local_socklen = ls->socklen;

#if (NGX_HAVE_UNIX_DOMAIN)
        if (c->sockaddr->sa_family == AF_UNIX) {
            c->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
            c->tcp_nodelay = NGX_TCP_NODELAY_DISABLED;
#if (NGX_SOLARIS)
            /* Solaris's sendfilev() supports AF_NCA, AF_INET, and AF_INET6 */
            c->sendfile = 0;
#endif
        }
#endif

        // 连接相关的读写事件
        rev = c->read;
        wev = c->write;

        // 建立连接后是可写的
        wev->ready = 1;

        // rtsig在nginx 1.9.x已经删除
        // linux里这段代码不执行
        if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
            rev->ready = 1;
        }

        // 如果listen使用了deferred,那么建立连接时就已经有数据可读了
        // 否则需要自己再加读事件,当有数据来时才能读取
        if (ev->deferred_accept) {
            rev->ready = 1;
#if (NGX_HAVE_KQUEUE || NGX_HAVE_EPOLLRDHUP)
            rev->available = 1;
#endif
        }

        rev->log = log;
        wev->log = log;

        /*
         * TODO: MT: - ngx_atomic_fetch_add()
         *             or protection by critical section or light mutex
         *
         * TODO: MP: - allocated in a shared memory
         *           - ngx_atomic_fetch_add()
         *             or protection by critical section or light mutex
         */

        // 连接计数器增加
        c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);

        c->start_time = ngx_current_msec;

#if (NGX_STAT_STUB)
        (void) ngx_atomic_fetch_add(ngx_stat_handled, 1);
#endif

        if (ls->addr_ntop) {
            c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len);
            if (c->addr_text.data == NULL) {
                ngx_close_accepted_connection(c);
                return;
            }

            c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen,
                                             c->addr_text.data,
                                             ls->addr_text_max_len, 0);
            if (c->addr_text.len == 0) {
                ngx_close_accepted_connection(c);
                return;
            }
        }

#if (NGX_DEBUG)
        {
        ngx_str_t  addr;
        u_char     text[NGX_SOCKADDR_STRLEN];

        ngx_debug_accepted_connection(ecf, c);

        if (log->log_level & NGX_LOG_DEBUG_EVENT) {
            addr.data = text;
            addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text,
                                     NGX_SOCKADDR_STRLEN, 1);

            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0,
                           "*%uA accept: %V fd:%d", c->number, &addr, s);
        }

        }
#endif

        // 如果事件机制不是epoll
        // 连接的读写事件都加入监控,即有读写都会由select/poll/kqueue等收集事件并处理
        if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
            if (ngx_add_conn(c) == NGX_ERROR) {
                ngx_close_accepted_connection(c);
                return;
            }
        }

        // 在linux上通常都使用epoll,所以上面的if代码不会执行
        // 需要在后续的处理流程中自己控制读写事件的监控时机

        log->data = NULL;
        log->handler = NULL;

        // 接受连接,收到请求的回调函数
        // 在http模块里是http.c:ngx_http_init_connection
        // stream模块里是ngx_stream_init_connection
        ls->handler(c);

        // epoll不处理
        if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
            ev->available--;
        }

    // 如果ev->available = ecf->multi_accept;
    // epoll尽可能接受多个请求,直至accept出错EAGAIN,即无新连接请求
    // 否则epoll只接受一个请求后即退出循环
    } while (ev->available);
}

惊群

todo ~~~

结束

至此,大致写完了,后面可能补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值