在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果。在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要通过结合源码来分析模块的初始化过程。

  稍微了解nginx的人都知道nginx是高度模块化的,各个功能都封装在模块中,而各个模块的初始化则是根据配置文件来进行的,下面我们会看到nginx边解析配置文件中的指令,边初始化指令所属的模块,指令其实就是指示怎样初始化模块的。

模块初始化框架

  模块的初始化主要在函数ngx_init_cycle(src/ngx_cycle.c)中下列代码完成:

 ngx_cycle_t *
 ngx_init_cycle(ngx_cycle_t *old_cycle)
 {
     ...
     //配置上下文
     cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
     ...
     //处理core模块,cycle->conf_ctx用于存放所有CORE模块的配置
     ; ngx_modules[i]; i++) {
         if (ngx_modules[i]->type != NGX_CORE_MODULE) {  //跳过不是nginx的内核模块
             continue;
         }
         module = ngx_modules[i]->ctx;
         //只有ngx_core_module有create_conf回调函数,这个会调用函数会创建ngx_core_conf_t结构,
         //用于存储整个配置文件main scope范围内的信息,比如worker_processes,worker_cpu_affinity等
         if (module->create_conf) {
             rv = module->create_conf(cycle);
             ...
             cycle->conf_ctx[ngx_modules[i]->index] = rv;
         }
     }
     //conf表示当前解析到的配置命令上下文,包括命令,命令参数等
     conf.args = ngx_array_create(pool, , sizeof(ngx_str_t));
     ...
     conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
     ...
     conf.ctx = cycle->conf_ctx;
     conf.cycle = cycle;
     conf.pool = pool;
     conf.log = log;
     conf.module_type = NGX_CORE_MODULE;  //conf.module_type指示将要解析这个类型模块的指令
     conf.cmd_type = NGX_MAIN_CONF;  //conf.cmd_type指示将要解析的指令的类型
     //真正开始解析配置文件中的每个命令
     if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
      ...
     }
     ...
     //初始化所有core module模块的config结构。调用ngx_core_module_t的init_conf,
     //在所有core module中,只有ngx_core_module有init_conf回调,
     //用于对ngx_core_conf_t中没有配置的字段设置默认值
     ; ngx_modules[i]; i++) {
         if (ngx_modules[i]->type != NGX_CORE_MODULE) {
             continue;
         }
         module = ngx_modules[i]->ctx;
         if (module->init_conf) {
             if (module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index])
                 == NGX_CONF_ERROR)
             {
                 environ = senv;
                 ngx_destroy_cycle_pools(&conf);
                 return NULL;
             }
         }
     }
     ...
 }

  cycle->conf_ctx是一个指针数组,数组中的每个元素对应某个模块的配置信息。ngx_conf_parse用户真正解析配置文件中的命令,conf存放解析配置文件的上下文信息,如module_type表示将要解析模块的类型,cmd_type表示将要解析的指令的类型,ctx指向解析出来信息的存放地址,args存放解析到的指令和参数。具体每个模块配信息的存放如下图所示,NGX_MAIN_CONF表示的是全局作用域对应的配置信息,NGX_EVENT_CONF表示的是EVENT模块对应的配置信息,NGX_HTTP_MAIN_CONF,NGX_HTTP_SRV_CONF,NGX_HTTP_LOC_CONF表示的是HTTP模块对应的main,server,local域的配置信息。

  下面我们来具体看下ngx_conf_parse函数是怎样解析每个指令的。

 char *
 ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
 {
     ...
     if (filename) {
         //打开配置文件
         fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, );
         ...
         prev = cf->conf_file;
         cf->conf_file = &conf_file;
         //获取配置文件信息
         if (ngx_fd_info(fd, &cf->conf_file->file.info) == NGX_FILE_ERROR) {
         ...
         }
         //配置缓冲区用于存放配置文件信息
         cf->conf_file->buffer = &buf;
         //NGX_CONF_BUFFER = 4096,直接malloc
         buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);
         ...
         //[pos,last)表示缓存中真正存储了数据的区间,[start,end)表示缓存区的物理区域
         buf.pos = buf.start;
         buf.last = buf.start;
         buf.end = buf.last + NGX_CONF_BUFFER;
         buf.temporary = ;
         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 = ;
         cf->conf_file->file.log = cf->log;
         cf->conf_file->line = ;
         type = parse_file;

     }
     ...
     for ( ;; ) {
         rc = ngx_conf_read_token(cf);  //从配置文件中读取下一个命令
         ...
         rc = ngx_conf_handler(cf, rc);  //查找命令所在的模块,执行命令对应的函数
         ...
     }
     ...
 }

  ngx_conf_parse是配置解析的入口函数,它根据当成上下文读取配置文件数据,进行分析的同时对配置文件语法进行检查。同时,如果遇到的指令包含块,这个函数会在指令处理函数修改作用域等上下文信息后,被间接递归调用来处理块中的配置信息。第一次调用ngx_conf_parse时,函数会打开配置文件,设置正在执行的解析类型,读取命令,然后调用ngx_conf_handler。我们先来看下ngx_conf_read_token怎么读取命令的:

 //解析一个命令和其所带的参数
 static ngx_int_t
 ngx_conf_read_token(ngx_conf_t *cf)
 {
     ...
     found = ;  //标记是否找到一个命令
     need_space = ;  //下一个字符希望是空格
     last_space = ;  //上一个字符是空格
     sharp_comment = ;  //注释
     variable = ;  //存在变量
     quoted = ;  //\符号
     s_quoted = ;  //单引号
     d_quoted = ;  //双引号
     ...
     for ( ;; ) {
         ch = *b->pos++;  //当前字符存储于ch中
         if (ch == LF) {  //换行
             if (sharp_comment) {  //注释结束,只有行注释
                 sharp_comment = ;
             }
         }
         if (sharp_comment) {  //跳过注释,直到换行
             continue;
         }
         if (quoted) {  //如果上一个字符是\,则跳过当前字符,后面读取token会做处理
             quoted = ;
             continue;
         }
         if (need_space) {  //次字符必须是space分割符
             /*
                 忽略掉space字符;space字符包括' ','\t',CR,LF
                 如果字符是';','{',此次命令读取结束;
                 如果字符是')',开始读取新的token;
                 如果读到其它字符则解析失败
             */
         }
         if (last_space) {  //表示一个token开始了,第一个非space字符
             /*
                 忽略掉space字符;
                 如果字符是';','{','}',此次命令读取结束;
                 如果字符是'#',字符后面是注释;
                 如果字符是'\',下一个字符将被忽略
                 如果字符是'"',''',下一个字符在单引号或双引号中
             */
         } else {  //开始读取新token
            /*
                 如果字符是'$',后面的非space字符组成变量token;
                 碰到了结束引号'"','''时,token读取完成;
                 碰到了space字符,token读取完成
            */
            if (found){
                 /*
                     将找到的token追加到cf->args数组中,并且每个token字符串以'\0'结束
                 */
            }
         }
     }
 }

  下面我们再来看下命令处理函数ngx_conf_handler:

 static ngx_int_t
 ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
 {
     ...
     ; ngx_modules[i]; i++) {
         cmd = ngx_modules[i]->commands;
         ...
         for ( /* void */ ; cmd->name.len; cmd++) {
             //命令名称对比
             //模块类型对比
             //命令类型对比
             //命令是否带块
             //检测命令参数
             conf = NULL;
             if (cmd->type & NGX_DIRECT_CONF) {
                 conf = ((void **) cf->ctx)[ngx_modules[i]->index];

             } else if (cmd->type & NGX_MAIN_CONF) {
                 conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);

             } else if (cf->ctx) {
                 confp = *(void **) ((char *) cf->ctx + cmd->conf);

                 if (confp) {
                     conf = confp[ngx_modules[i]->ctx_index];
                 }
             }
             //调用命令中的set函数
             rv = cmd->set(cf, cmd, conf);
             ...
         }
     }
     ...
 }

  完全读取到一条配置指令后,Nginx会使用该指令名在所有指令定义中进行查找。但是,在进行查找之前,Nginx会先验证模块的类型和当前解析函数上下文中的类型是否一致。随后,在某个模块中找到匹配的指令定义后,还会验证指令可以出现的作用域是否包含当前解析函数上下文中记录的作用域。最后,检查指令的参数个数是否和指令定义中标明的一致。

  校验工作完成后,Nginx将指令名和所有模块预定义支持的指令进行对比,找到完全匹配的配置指令定义。根据配置指令的不同类型,配置项的存储位置也不同。

  NGX_DIRECT_CONF类型的配置指令,其配置项存储空间是全局作用域对应的存储空间。这个类型的指令主要出现在ngx_core_module模块里。

 conf = ((void **) cf->ctx)[ngx_modules[i]->index];

  NGX_MAIN_CONF表示配置指令的作用域为全局作用域。纵观Nginx整个代码,除了ngx_core_module的配置指令(同时标识为NGX_DIRECT_CONF)位于这个作用域中外,另外几个定义新的子级作用域的指令–events、http、mail、imap,都是非NGX_DIRECT_CONF的NGX_MAIN_CONF指令,它们在全局作用域中并未被分配空间,所以在指令处理函数中分配的空间需要挂接到全局作用域中,故传递给指令处理函数的参数是全局作用域的地址。

 conf = &(((void **) cf->ctx)[ngx_modules[i]->index];

  其它类型配置指令项的存储位置和指令出现的作用域(并且非全局作用域)有关:

 confp = *(void **) ((char *) cf->ctx + cmd->conf);
 if (confp) { conf = confp[ngx_modules[i]->ctx_index]; }

  配置项将要存储的位置确定后,调用指令处理函数,完成配置项初始化和其它工作。

rc = cmd->set(cf, cmd, conf);

  下面通过分析模块HTTP的初始化来进一步加深理解模块的初始化。

HTTP模块的初始化

  http模块由很多个模块组成,这里主要讲解两个主要模块的初始化,它们是ngx_http_module和ngx_http_core_module。ngx_http_moduel模块很简单,它的定义如下:

 static ngx_command_t  ngx_http_commands[] = {
     { ngx_string("http"),  //http命令描述结构
       NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
       ngx_http_block,
       ,
       ,
       NULL },
       ngx_null_command
 };
 static ngx_core_module_t  ngx_http_module_ctx = {
     ngx_string("http"),
     NULL,
     NULL
 };
 ngx_module_t  ngx_http_module = {
     NGX_MODULE_V1,
     &ngx_http_module_ctx,                  /* module context */
     ngx_http_commands,                     /* module directives */
     NGX_CORE_MODULE,                       /* module type */
     ...
 };

  从模块的定义看,它是一个NGX_CORE_MODULE类型的模块,就包含一条指令http,当在全局作用域中遇到http指令后,会调用ngx_http_block函数,传递给该函数的参数为:cf,当前配置上下文信息;cmd,http命令描述结构;conf,ngx_http_module模块在全局作用域数组中的地址。下面看下ngx_http_block函数:

 static char *
 ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     ...
     ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));  //http main配置结构
     ...
     *(ngx_http_conf_ctx_t **) conf = ctx;  //将http main配置结构挂到全局作用域上
     //计算NGX_HTTP_MODULE类型模块的个数,并计算每个NGX_HTTP_MODULE模块在全部NGX_HTTP_MODULE模块中的下标
     ngx_http_max_module = ;
     ; ngx_modules[m]; m++) {
         if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
             continue;
         }
         ngx_modules[m]->ctx_index = ngx_http_max_module++;
     }
     //http的main域配置
     ctx->main_conf = ngx_pcalloc(cf->pool,
                                  sizeof(void *) * ngx_http_max_module);
     ...
     //http的server域,用来合并server块中的域
     ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
     ...
     //http的local域,用来合并server块中的local块中的域
     ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
     //创建各个作用域对应的配置结构
     ; ngx_modules[m]; m++) {
         if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
             continue;
         }
         module = ngx_modules[m]->ctx;
         mi = ngx_modules[m]->ctx_index;
         if (module->create_main_conf) {
             ctx->main_conf[mi] = module->create_main_conf(cf);
             ...
         }
         if (module->create_srv_conf) {
             ctx->srv_conf[mi] = module->create_srv_conf(cf);
             ...
         }
         if (module->create_loc_conf) {
             ctx->loc_conf[mi] = module->create_loc_conf(cf);
             ...
         }
     }
     pcf = *cf;
     cf->ctx = ctx;  //更新配置上下文为http的上下文
     ...
     //递归ngx_conf_parse来调用处理http包含的块的配置信息
     cf->module_type = NGX_HTTP_MODULE;  //模块类型为NGX_HTTP_MODULE
     cf->cmd_type = NGX_HTTP_MAIN_CONF;  //指令的作用域
     rv = ngx_conf_parse(cf, NULL);
     ...
     *cf = pcf;  //恢复配置上下文
     ...
 }

  到这里,已经创建的配置信息如下图所示:

  递归调用ngx_conf_parse后,接下来处理的每个指令都是NGX_HTTP_MAIN_CONF作用域的,每个指令的处理函数会初始化应配置结构信息,而具体的配置信息存放在哪个数组里面由指令定义中的conf字段决定,即一定是main_conf,srv_conf和loc_conf之中的一种。NGX_HTTP_MAIN_CONF作用域中的很多指令都属于ngx_http_core_module模块,下面看下这个模块的定义:

 static ngx_command_t  ngx_http_core_commands[] = {
     ...
     { ngx_string("server"),  //server指令
       NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
       ngx_http_core_server,
       ,
       ,
       NULL },
     { ngx_string("location"),  //location指令
       NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
       ngx_http_core_location,
       NGX_HTTP_SRV_CONF_OFFSET,
       ,
       NULL },
     ...
 }

 static ngx_http_module_t  ngx_http_core_module_ctx = {
     ngx_http_core_preconfiguration,        /* preconfiguration */
     NULL,                                  /* postconfiguration */
     ngx_http_core_create_main_conf,        /* create main configuration */
     ngx_http_core_init_main_conf,          /* init main configuration */
     ngx_http_core_create_srv_conf,         /* create server configuration */
     ngx_http_core_merge_srv_conf,          /* merge server configuration */
     ngx_http_core_create_loc_conf,         /* create location configuration */
     ngx_http_core_merge_loc_conf           /* merge location configuration */
 };
 ngx_module_t  ngx_http_core_module = {
     NGX_MODULE_V1,
     &ngx_http_core_module_ctx,             /* module context */
     ngx_http_core_commands,                /* module directives */
     NGX_HTTP_MODULE,                       /* module type */
     ...
 };

  这个模块定义了很多指令,其中server和location是比较关键的两条指令,它们分别处理server和location指令后面所带有的块。处理这些块的方法和处理http指令后面的块的方法类似,都是修改配置上下文,调用ngx_conf_parse来处理,当然在处理server块时只会生成srv_conf和loc_conf数组,所有的server块共享一个main_conf数组,在处理location块时只会生成loc_conf数组,同一个server块中的location共享这个server块中的ser_conf数组。

  有些指令能同时出现在http块,server块和location块中,并且底层块中指令会覆盖上层块中的定义,如果底层块中指令没定义,上层块的指令定义会传递到底层块中。所以在函数ngx_http_block调用ngx_conf_parse解析处理所有的http模块的配置指令后,调用ngx_http_merge_servers将http块中的srv_conf信息传递到各个server块中,在ngx_http_merge_servers中将server块中的loc_conf信息合并到所有的locations块中。

nginx源码分析之模块初始化的更多相关文章

  1. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  2. nginx源码分析——event模块

    源码:nginx 1.12.0   一.简介      nginx是一款非常受欢迎的软件,具备高性能.模块化可定制的良好特性.之前写了一篇nginx的http模块分析的文章,主要对http处理模块进行 ...

  3. nginx源码分析——http模块

         源码:nginx 1.12.0      一.nginx http模块简介           由于nginx的性能优势,现在已经有越来越多的单位.个人采用nginx或者openresty. ...

  4. Nginx源码分析--epoll模块

    Nginx采用epoll模块实现高并发的网络编程,现在对Nginx的epoll模块进行分析. 定义在src/event/modules/ngx_epoll_module.c中 1. epoll_cre ...

  5. Nginx源码分析:3张图看懂启动及进程工作原理

    编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享.转载请注明来自高可用架构公众号「ArchNotes」.   导读:很多工程师及架构师都希望了解及掌握高性能服务器 ...

  6. nginx源码分析线程池详解

    nginx源码分析线程池详解 一.前言     nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,n ...

  7. SDL2源码分析1:初始化(SDL_Init())

    ===================================================== SDL源码分析系列文章列表: SDL2源码分析1:初始化(SDL_Init()) SDL2源 ...

  8. nginx源码分析-源码结构

    本文主要简单介绍nginx源码目录结构.程序编译流程.如何构建学习nginx的环境等.本文以及后续nginx源码分析文章是基于nginx当前(2009-02-27)的稳定版本0.6.35进行的分析,该 ...

  9. Spring IOC 容器源码分析 - 余下的初始化工作

    1. 简介 本篇文章是"Spring IOC 容器源码分析"系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bea ...

随机推荐

  1. autofac 组件的实例范围

    实例范围决定如何在请求之间共享服务. 原文地址:http://docs.autofac.org/en/latest/lifetime/instance-scope.html 每个依赖一个实例 使用这个 ...

  2. java.lang.IllegalArgumentException: 'sessionFactory' or 'hibernateTemplate' is required

    java.lang.IllegalArgumentException: 'sessionFactory' or 'hibernateTemplate' is required 严重: Exceptio ...

  3. 【阿里云产品公测】利用PTS服务优化网站数据库读写性能

    [阿里云产品公测]利用PTS服务优化网站数据库读写性能 作者:阿里云用户千鸟 写这个帖子主要也是因为在用PTS测试网站的时候,手动访问网站进入报错页面,主要原因是数据库连接对象存在问题,导致并发多的时 ...

  4. Uva 552 Prime Ring Problem(dfs)

    题目链接:Uva 552 思路分析:时间限制为3s,数据较小,使用深度搜索查找所有的解. 代码如下: #include <iostream> #include <string.h&g ...

  5. Java 水仙花数

    小小练习大神掠过吧 题目:打印出所有的"水仙花数",所谓"水仙花数"是指一个三位数,其各位数字立方和等于该数本身.例如:153是一个"水仙花数&quo ...

  6. Cisco配置aaa验证

    当您的网络中部署了一台集中的radius校验服务器(比如我司的SAM,cisco的ACS等),希望对登陆设备的用户身份进行合法性校验,而账号都统一由该radius服务器集中产生与维护,您希望所有的登入 ...

  7. DS18B20初上电显示85℃问题

    以前用的温度采集都是用的AD,这次改为了DS18B20,看了资料,没有很复杂的部分,重要的就是时序.板子出来后初步测试也能正常读取温度,然而有个问题比较奇怪,就是在板子初上电时读取温度总是显示为+85 ...

  8. ssh原理图解

    SSH(Secure Shell)是一种能够以安全的方式提供远程登录的协议,也是目前远程管理Linux系统的首选方式.在此之前,远程登录一般常用FTP和Telnet,但是它们以明文的形式在网络中传输账 ...

  9. Redis服务搭建与基础功能示例

    一.Redis简介 Redis是一个非关系型远程内存数据库,它也是一个Key-value模型的数据库.Redis支持5种数据类型(string.list.set.sorted set.hash),可以 ...

  10. 二层协议--MPLS协议总结

    1.MPLS是介于2层和3层之间的协议,主要应用在城域网中,作为集客专线.基站等承载VPN技术的关键技术. 2.MPLS利用MPLS标签进行转发,先通过IP单播路由的方式沿途分配好MPLS标签,分配完 ...