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->
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值