对于一个服务器来说,事件模型是至关重要的,nginx本身的高性能也要归功于其优秀的事件模型。一般地,nginx的事件模型是基于epoll的,而使用epoll需要调用epoll_create、epoll_ctl和epoll_wait三个函数。由于nginx本身的松耦合模块机制,这些函数的调用被隐藏在很难发现的地方,本篇文章就来介绍nginx的事件模型的初始化过程,从而大家可以清晰的知道epoll各个函数的具体调用位置。
1. 重要的数据结构
1. ngx_event_actions_t
这个结构是nginx底层事件模型的抽象,具体的io模型会有自己的实现,比如epoll、select。通过这一层的抽象屏蔽了底层的不同实现,我们可以轻易从一种模型迁移至其他模型。这实际上就是C的面向接口编程,值得学习。在ngx_event.c中定义了全局变量ngx_event_actions,他指向具体的底层实现,在底层模型init函数中被赋值。
typedef struct {
ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
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);
ngx_int_t (*add_conn)(ngx_connection_t *c);
ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
ngx_uint_t flags);
ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
2. 事件模型
在前文中已经介绍过,ngx_events_module是一个core module,由它来完成event module的初始化。当我们查看objs/ngx_modules.c文件中的ngx_moduels数组(保存所有nginx模块)时,会发现只有两个event module,分别是ngx_event_core_module和ngx_epoll_module。ngx_event_core_module这个模块在事件模型初始化过程中起着至关重要的作用,而ngx_epoll_module实际上就是底层io模型的实现。事件模型的初始化与http模块类似,由ngx_events_module驱动整个事件模块的解析和初始化,ngx_event_core_module对events块大部分指令的解析保存重要的配置信息。下面就来具体看看事件模型的初始化,这里依然采用之前的方式——按照nginx执行的流程,也就是事件模型的初始化顺序。
1. ngx_events_block
与ngx_http_module类似,ngx_events_module只有一个指令:events,这是一个块指令,而它的回调函数ngx_events_block就是事件模型初始化的入口。
ngx_event_max_module = 0;
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
ngx_modules[i]->ctx_index = ngx_event_max_module++;
}
还是与http模块一样,对所有的event module计数,同时更新模块的ctx_index,还记得这个变量吗?它就是该模块的配置结构在具体类型(http、event...)配置结构数组中的下标。
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;
}
/**
* 将在ngx_cycle->conf_ctx数组中存放的ngx_events_module的config信息赋值为ctx
* 也就是所有event module配置信息的数组。
*/
*(void **) conf = ctx;
为ctx分配空间,所有event module的全局配置就是一个数组,这里也为它分配空间,同时将存放在ngx_cycle->conf_ctx数组的ngx_events_module的配置结构赋值为ctx。
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
m = ngx_modules[i]->ctx;
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;
}
}
}
调用所有event module的create_conf回调函数创建配置结构。
/**
* 为解析events块准备,设置要解析的模块以及指令的类型。
* 备份cf,解析完events块后恢复。
*/
pcf = *cf;
cf->ctx = ctx;
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;
/**
* 解析events块
*/
rv = ngx_conf_parse(cf, NULL);
开始解析events块。前面已经介绍过nginx中只有ngx_event_core_module和ngx_epoll_module两个event module。解析events块就是解析这两个模块的指令,而它们的指令主要是一些配置信息,比如worker_connection指令设置每个worker的连接数,实际上就是为ngx_cycle->connection_n赋值而已,accept_mutex指令就是指定是否使用accept锁,所以这部分内容并不影响事件模块的初始化,所以这不再赘述。
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;
}
}
}
解析完events块,接着调用所有event module的init_conf回调函数初始化模块的配置结构。这里ngx_event_core_module和ngx_epoll_module会对配置结构中尚未初始化的一些属性赋默认值,比如默认使用io模型,也就是use指令的默认值。
看到这里,大家可能好奇events块已经解析完毕