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