前言
在上一小节中我们对模块的整体有了一定的把握,本小节将进入到事件模块的分析中,了解nginx是如何收集、管理、分发事件的。nginx将网络事件以及定时事件集成到一起进行管理,由于各平台的I/O多路复用机制不同,但是nginx支持多个操作系统,因此在事件模块中也实现了多种针对不同平台下封装I/O多路复用机制的模块。由于我所用的环境主要关注的是linux,因此后面主要分析ngx_epoll_module
。
事件模块具体化的通用性接口
前面说过,每一个模块都会遵循ngx_module_t
通用性接口,里面有个ctx
成员,它是一个泛型指针,可以转换为其它任何类型,因为它的存在可以让各类型模块的接口更加具体化。
对应于事件模块,则是ngx_event_module_t
:
typedef struct {
//核心模块名字
ngx_str_t *name;
//在解析配置项前,调用该方法创建用于存储配置项的数据结构
void *(*create_conf)(ngx_cycle_t *cycle);
//在解析配置项后,调用该方法用于处理当前事件模块感兴趣的配置项
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
//对于I/O多路复用机制,每个事件模块需要实现的接口
ngx_event_actions_t actions;
} ngx_event_module_t;
ngx_event_actions_t:
typedef struct {
//将事件添加到I/O多路复用机制中
ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
//将事件从I/O多路复用机制的监听中移除
ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
//启用事件,目前并没有使用
ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
//禁用事件,目前并没有使用
ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
//添加一个新连接到I/O多路复用机制中(这意味着该连接对应的读和写事件也已经添加到了该I/O多路复用机制中了)
ngx_int_t (*add_conn)(ngx_connection_t *c);
//从I/O多路复用机制中移除一个连接的读写事件
ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
//在多线程下使用,nginx目前并没有以多线程的方式运行
ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
//通过该方法来处理事件。会被ngx_process_events_and_timers调用
//它是处理以及分发事件的核心
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
ngx_uint_t flags);
//初始化事件驱动模块(例如ngx_epoll_module模块)
ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
//退出事件模块驱动前调用的方法
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
ngx_event_module_t
有了ngx_event_actions_t
这个成员,我们就可以把所有的事件驱动模块统一起来了,无论是使用epoll、poll或者kqueue,对于使用事件模块的其他模块来说都不重要。
ngx_events_module所做的工作
要知道核心模块中的模块与其他类型的模块其实有一定的联系,我们可以让核心模块中模块对另外一个类型的模块进行初始化等工作,而其他比较具体的工作,就让另外类型的模块来完成,这样就可以让主框架更加简化(只关注核心模块),加强模块化的设计。
ngx_events_module
的定义,实现ngx_module_t
通用接口(event/ngx_event.c):
ngx_module_t ngx_events_module = {
/* NGN_MODULE_V1在core/ngx_conf_file.h中宏定义
* #define NGX_MODULE_V1 0, 0, 0, 0, 0, 0, 1
* 相当于将ctx_index、index、spare0-3、version这几个成员赋值了
*/
NGX_MODULE_V1,
/* 该成员对应于ctx
* 即核心模块的具体化接口
*/
&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同样也在core/ngx_conf_file.h中定义
* #define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0
* 之前展开ngx_module_t时说过这几个成员起占位作用,方便后面进行扩展
*/
NGX_MODULE_V1_PADDING
};
关于ngx_events_module_ctx
,它同样也在event/ngx_event.c中定义:
static ngx_core_module_t ngx_events_module_ctx = {
//name
ngx_string("events"),
//create_conf
NULL,
//init_conf
NULL
};
关于ngx_events_commands
,同样也在event/ngx_event.c中定义:
static ngx_command_t ngx_events_commands[] = {
{ ngx_string("events"), //name
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, //type
ngx_events_block, //set
0, //conf
0, //offset
NULL //post },
ngx_null_command
};
知道了这一系列的定义之后,我们可以来分析一下ngx_events_module
模块是通过什么来替事件模块做一些初始化工作以及在什么时候做的。
首先我们看到ngx_events_module_ctx
中的create_conf
和init_conf
成员都赋为NULL,这证明该模块并不需要自己存储一些配置项,因此在main
函数中遍历ngx_modules
数组调用create_conf
/init_conf
时并不关ngx_events_module
的事。
那么我们下一个关注点就应该放在commands
成员中的set
成员上了,它是一个函数指针,当解析了nginx.conf
配置文件后会被调用。
还记得是什么时候开始解析的nginx.conf
文件吗?
之前在第二节分析启动流程时,在ngx_init_cycle
函数中会调用ngx_conf_parse
对nginx.conf
文件进行第一次解析,其中会调用ngx_conf_handler
,对读取到的配置项遍历ngx_modules
数组,如果有模块对该配置项感兴趣,则会调用其commands
成员中的set
指向的函数对该配置项进行处理。
因此,ngx_events_block
函数将在ngx_conf_parse
扫描nginx.conf
文件扫描到”events {}”时,进入到ngx_conf_handler
被调用。这一切都是在ngx_init_cycle
中完成的。
了解了这些,就已经得知ngx_events_module
是通过ngx_events_block
函数替事件模块做初始化工作并且是在ngx_init_cycle
中通过ngx_conf_parse
中的ngx_conf_handler
里遍历ngx_modules
数组最终调用commands
成员的set
成员指向的函数完成。
可能有点绕,这里附上调用图(花的有点丑):
接下来,我们就只需要知道ngx_events_block
做了什么就明白核心模块中的ngx_events_module
为事件模块所做的全部工作了。
该函数的源码在event/ngx_event.c
中。
static char *
ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
//三级指针
//首先它是一个指针
//然后指向了一个指针数组
//因此是三级指针
void ***ctx;
ngx_uint_t i;
ngx_conf_t pcf;
ngx_event_module_t *m;
/* count the number of the event modules and set up their indices */
ngx_event_max_module = 0;
//遍历ngx_modules数组
for (i = 0; ngx_modules[i]; i++) {
//过滤掉type不为NGX_EVENT_MODULE的模块
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
/* 初始化ctx_index
* ctx_index为该类型模块中的模块内部顺序
* 第一个是ngx_event_core_module
* 毕竟它要负责选择事件驱动模块
*/
ngx_modules[i]->ctx_index = ngx_event_max_module++;
}
/* 分配指针数组,存储所有事件模块生成的配置项结构体指针
* 也就是说,所有事件模块生成的配置项结构体指针会被ngx_events_module模块统一起来,形成一个指针数组
* 然后ngx_cycle_t中的conf_ctx存储着每个核心模块的配置结构体指针,该指针就指向了上面形成的指针数组
*/
//申请指向指针数组的指针的空间
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;
}
/* 注意!!!!!conf参数是ngx_events_module中的ctx成员
* 想象一下,ngx_events_module模块需要存储指向所有事件模块的配置项结构体指针形成的指针数组的指针
* 但是介绍ngx_module_t接口时其中好像并没有专门的成员去存储它
* 不,其实有。ctx成员就承担了这个角色,它之前是指向ngx_core_module_t的,但是现在它的工作发生了变化!
* 它改为存储指向所有事件模块的配置项结构体指针形成的指针数组的指针了!也就是该函数中的ctx
* 这一点需要特别注意!如果没有理解到,建议再多看几遍!
* 这样ngx_cycle_t中的conf_ctx与所有事件模块的配置项结构体都联系起来了!
* 这一部分全是指针,希望你不会看晕
*/
*(void **) conf = ctx;
//遍历ngx_modules数组
for (i = 0; ngx_modules[i]; i++) {
//过滤掉非NGX_EVENT_MODULE模块
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
//指向事件模块所具有的通用接口ngx_event_module_t
m = ngx_modules[i]->ctx;
/* 调用其中的create_conf方法
* 创建存储配置项的结构体指针
*/
if (m->create_conf) {
//将该结构体指针存储起来
(*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle);
if ((*ctx)[ngx_modules[i]->ctx_index] == NULL) {
return NGX_CONF_ERROR;
}
}
}
//为所有的事件模块解析nginx.conf配置文件
pcf = *cf;
cf->ctx = ctx;
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;
/* ngx_conf_parse内部会读取配置文件
* 若找到对当前读取到的配置项感兴趣的模块
* 则会调用该模块中通用接口里commands成员中set指向的函数
* 进行配置项的解析
*/
rv = ngx_conf_parse(cf, NULL);
*cf = pcf;
if (rv != NGX_CONF_OK)
return rv;
//接着调用所有事件模块的init_conf方法
//将各事件模块对应的存储配置项的结构体成员中还未设立初值的设置为默认值
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
m = ngx_modules[i]->ctx;
if (m->init_conf) {
rv = m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]);
if (rv != NGX_CONF_OK) {
return rv;
}
}
}
return NGX_CONF_OK;
}
简单的来说,该函数大致做了以下工作:
1. 初始化所有事件模块的ctx_index
序号。(ctx_index
是所有模块都必须实现的ngx_module_t
接口中的成员,代表某种类型模块中内部模块的序号,它可以用于获取配置项结构体指针)
2. 创建存储所有事件模块生成的配置项结构体指针的数据结构,指针数组
3. 调用所有事件模块的create_conf方法,产生的结构体指针就存储在第2步产生的数据结构中
4. 替所有的事件模块解析nginx.conf文件,当在nginx.conf文件中发现”event {}”这种事件模块感兴趣的配置项时,会回调所有事件模块ngx_module_t
中的ngx_command_t commands
成员实现的配置项解析的方法
5. 在解析完了配置项之后,调用所有事件模块都会实现的init_conf
方法(注意是事件模块具体化的接口而不是全部模块的接口)
conf_ctx如何获取到事件模块的配置项结构体指针
如果你明白了上面的内容,那这个问题就比较简单了。
ngx_events_module
定义了一个宏来完成这个功能。
在event/ngx_event.h中定义:
#define ngx_event_get_conf(conf_ctx, module) \
(*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index];
ngx_get_conf
也是宏函数,在core/ngx_conf_file.h中定义:
#define ngx_get_conf(conf_ctx, module) conf_ctx[module.index]
只用传入conf_ctx
以及该模块的ngx_module_t
通用接口,就可以获取到该模块的配置项结构体指针。这里需要明确ctx_index
和index
的区别,前面已经提过很多次了,这里再说一遍,ctx_index
是该类型模块中各模块的内部序号,而index
则是所有模块的序号。
ngx_event_get_conf
展开之后是这样的:(*(conf_ctx[ngx_events_module.index])) [module.ctx_index];
,解释一下,(*(conf_ctx[module.index]))
获取到ngx_events_module
核心模块指向事件模块的配置项结构体指针的指针数组,然后通过传入的module
,获取到具体模块在事件模块中的序号,然后取值,就自然获取到了该模块的配置项结构体指针。
可能说起来有点抽象,这里引用《深入理解Nginx》书中的图来帮助理解:
小结
到此为止,我们已经明白了核心模块中的ngx_events_module模块与事件模块产生的联系,以及它做的前期工作还有如何获取到事件模块中的配置项结构体指针,可能理解起来不是那么容易,因为既涉及到了主框架也涉及到了模块,建议如果不理解的话多看几遍,书上所讲的比较宽泛,没有太具体到代码。
下面我们就可以真正进入到事件模块之中了。