nginx的大部分功能都采用模块化的方式加载到主程序,通过配置文件实现模块的各种加载和参数配置。模块化架构分析是nginx最关键的部分之一,本文作为参考文献[1]的补充,深入分析nginx如何把配置文件和模块管理联系到一起的。 可以说整个nginx都是由模块来组成。nginx的模块可以分为四种:core、
event、http和mail, core是核心模块,event是事件处理模块,本文重点以这两个模块为例进行分析。在编译配置的时候,由自动脚本生成的objs/ngx_modules.c,定义了要包含的哪些模块,保存到全局变量ngx_modules[] 中,
ngx_module_t *ngx_modules[] = {
&ngx_core_module,
&ngx_errlog_module,
&ngx_conf_module,
&ngx_events_module,
&ngx_event_core_module,
&ngx_epoll_module,
&ngx_http_module,
//.省略
NULL
}
而ngx_core_module, ngx_events_module这些全局变量,则在模块的实现文件中定义。ngx_module_t的结构的主要成员见下图。
commands是ngx_command_t的数组,表示该模块支持的配置命令。
ctx是模块上下文,保存着该模块的关键的信息,比如event模块上下文为ngx_event_module_t结构,该结构有一个ngx_event_actions_t成员,保存着事件处理函数,它把事件处理抽象成几个函数(添加/删除事件,查询事件,事件响应),具体的epoll, select, kqueue等API根据配置来填入,下文将详细讲述。 模块上下文一般都有create_conf和init_conf两个钩子,由于每个模块都有一个属于自己的配置结构体,用来保存该模块的配置参数,这两个函数钩子就是创建和初始化该模块的配置结构体。
nginx的进程启动过程可参考文献[1],下面就一步步分析从配置文件到模块载入和配置的整个过程。
nginx的配置文件示例如下图
#user nobody;
worker_processes 10;
error_log /home/weblogs/error.log crit;
#error_log logs/error.log notice;
pid logs/nginx.pid;
worker_rlimit_nofile 51200;
events {
use epoll;
worker_connections 51200;
}
http {
server {
server_name xxx;
listen 80;
}
.
}
可以看到,nginx配置文件格式是:
配置节名(也是命令的一种) {
下一级配置节名{
。。。 。。。
}
命令 值
}
这里的命令就是上文的ngx_command_t结构中的name字符串,最外层的是对应ngx_core_module中的命令。整个配置过程如下:
main()
{
....
//
所有模块点一下数, 初始化index
ngx_max_module = 0;
for (i = 0;
ngx_modules[i]; i++) {
ngx_modules[i]->index = ngx_max_module++;
} ngx_init_cycle(); //初始化并设置全局变量 ///..........
ngx_master_process_cycle(); //启动进程,执行主循环干活, 参见文档[2]
///..........
}
//设置全局变量
ngx_init_cycle(){
for (i
= 0;
ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_CORE_MODULE) {
continue;
}
module = ngx_modules[i]->ctx; //取得模块上下文, 例如ngx_core_module中的ngx_core_module_ctx
//
调度core类型模块的钩子create_conf,并且把创建的配置结构体变量存放到cycle->conf_ctx中
if (module->create_conf) {
rv = module->create_conf(cycle);
if (rv == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
cycle->conf_ctx[ngx_modules[i]->index]
= rv; //这里是配置信息的结构体
}
}
/* 例如ngx_core_module模块,create_conf钩子是调用ngx_core_module_create_conf, 该函数创建一个ngx_core_conf_t结构体,保存了全局的配置信息,比如子进程数,连接数,文件路径等顶层信息,并返回。之后保存在全局cycle的conf_ctx对应位置(对应于模块index)。 */
//....
ngx_conf_parse(&conf, &cycle->conf_file); //分析配置文件
//
调度core模块的钩子init_conf,设置刚才创建的配置结构体变量(用从配置文件中读取的数据赋值)
init_conf根据之前的conf_parse结果,填充配置结构体数值
//
调度所有模块的init_module钩子,初始化模块
for (i = 0;
ngx_modules[i]; i++) {
if (ngx_modules[i]->init_module) { //调用模块的初始化函数
if (ngx_modules[i]->init_module(cycle) != NGX_OK)
{
exit(1);
}
}
} .....
}
ngx_conf_parse()
{
//循环对配置文件进行语法分析
ngx_conf_read_token(); //读取下一个token
//根据{ }来设置ngx_conf_s中的变量,改变当前的模块配置节
ngx_conf_handler(); //分析该配置节下的模块命令.
}
ngx_conf_handler(){
//1. 遍历模块的command_s数组,strcmp来比对命令
//2. 根据command_t的参数指定,读取后续参数
//3. 获取该模块的配置结构体,以及该命令相关的参数在配置结构体中的偏移量(根据command_t中的offset)
//4. 调用command_t中的set钩子,把读到参数值赋值给配置结构体
}
最后在模块的init_conf函数中可以根据配置值,来做相应的操作。
比如底层的事件处理库,use指令指示用select还是epoll呢,比如use epoll
那么这些event模块在init的时候,就会判断配置结构体中的use, 如果是自己,则把event_actions赋值给全局变量ngx_event_actions,
这样,其他模块就可以通过ngx_event_actions中的钩子进行事件处理操作, 隔离了底层实现。
参考文献
[1] nginx源码分析--模块化(1) http://blog.sina.com.cn/s/blog_677be95b0100iive.html
[2] nginx的进程模型。http://simohayha.javaeye.com/blog/467940