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

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

模块初始化框架

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

 1 ngx_cycle_t *
 2 ngx_init_cycle(ngx_cycle_t *old_cycle)
 3 {
 4     ...
 5     //配置上下文
 6     cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
 7     ...
 8     //处理core模块,cycle->conf_ctx用于存放所有CORE模块的配置
 9     for (i = 0; ngx_modules[i]; i++) {
10         if (ngx_modules[i]->type != NGX_CORE_MODULE) {  //跳过不是nginx的内核模块
11             continue;
12         }
13         module = ngx_modules[i]->ctx;
14         //只有ngx_core_module有create_conf回调函数,这个会调用函数会创建ngx_core_conf_t结构,
15         //用于存储整个配置文件main scope范围内的信息,比如worker_processes,worker_cpu_affinity等
16         if (module->create_conf) {
17             rv = module->create_conf(cycle);
18             ...
19             cycle->conf_ctx[ngx_modules[i]->index] = rv;
20         }
21     }
22     //conf表示当前解析到的配置命令上下文,包括命令,命令参数等
23     conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
24     ...
25     conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
26     ...
27     conf.ctx = cycle->conf_ctx;
28     conf.cycle = cycle;
29     conf.pool = pool;
30     conf.log = log;
31     conf.module_type = NGX_CORE_MODULE;  //conf.module_type指示将要解析这个类型模块的指令
32     conf.cmd_type = NGX_MAIN_CONF;  //conf.cmd_type指示将要解析的指令的类型
33     //真正开始解析配置文件中的每个命令
34     if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
35      ...
36     }
37     ...
38     //初始化所有core module模块的config结构。调用ngx_core_module_t的init_conf,
39     //在所有core module中,只有ngx_core_module有init_conf回调,
40     //用于对ngx_core_conf_t中没有配置的字段设置默认值
41     for (i = 0; ngx_modules[i]; i++) {
42         if (ngx_modules[i]->type != NGX_CORE_MODULE) {
43             continue;
44         }
45         module = ngx_modules[i]->ctx;
46         if (module->init_conf) {
47             if (module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index])
48                 == NGX_CONF_ERROR)
49             {
50                 environ = senv;
51                 ngx_destroy_cycle_pools(&conf);
52                 return NULL;
53             }
54         }
55     }
56     ...
57 }

  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函数是怎样解析每个指令的。

 1 char *
 2 ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
 3 {
 4     ...
 5     if (filename) {
 6         //打开配置文件
 7         fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
 8         ...
 9         prev = cf->conf_file;
10         cf->conf_file = &conf_file;
11         //获取配置文件信息
12         if (ngx_fd_info(fd, &cf->conf_file->file.info) == NGX_FILE_ERROR) {
13         ...
14         }
15         //配置缓冲区用于存放配置文件信息
16         cf->conf_file->buffer = &buf;
17         //NGX_CONF_BUFFER = 4096,直接malloc
18         buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);
19         ...
20         //[pos,last)表示缓存中真正存储了数据的区间,[start,end)表示缓存区的物理区域
21         buf.pos = buf.start;
22         buf.last = buf.start;
23         buf.end = buf.last + NGX_CONF_BUFFER;
24         buf.temporary = 1;
25         cf->conf_file->file.fd = fd;
26         cf->conf_file->file.name.len = filename->len;
27         cf->conf_file->file.name.data = filename->data;
28         cf->conf_file->file.offset = 0;
29         cf->conf_file->file.log = cf->log;
30         cf->conf_file->line = 1;
31         type = parse_file;
32
33     }
34     ...
35     for ( ;; ) {
36         rc = ngx_conf_read_token(cf);  //从配置文件中读取下一个命令
37         ...
38         rc = ngx_conf_handler(cf, rc);  //查找命令所在的模块,执行命令对应的函数
39         ...
40     }
41     ...
42 }

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

 1 //解析一个命令和其所带的参数
 2 static ngx_int_t
 3 ngx_conf_read_token(ngx_conf_t *cf)
 4 {
 5     ...
 6     found = 0;  //标记是否找到一个命令
 7     need_space = 0;  //下一个字符希望是空格
 8     last_space = 1;  //上一个字符是空格
 9     sharp_comment = 0;  //注释
10     variable = 0;  //存在变量
11     quoted = 0;  //\符号
12     s_quoted = 0;  //单引号
13     d_quoted = 0;  //双引号
14     ...
15     for ( ;; ) {
16         ch = *b->pos++;  //当前字符存储于ch中
17         if (ch == LF) {  //换行
18             if (sharp_comment) {  //注释结束,只有行注释
19                 sharp_comment = 0;
20             }
21         }
22         if (sharp_comment) {  //跳过注释,直到换行
23             continue;
24         }
25         if (quoted) {  //如果上一个字符是\,则跳过当前字符,后面读取token会做处理
26             quoted = 0;
27             continue;
28         }
29         if (need_space) {  //次字符必须是space分割符
30             /*
31                 忽略掉space字符;space字符包括' ','\t',CR,LF
32                 如果字符是';','{',此次命令读取结束;
33                 如果字符是')',开始读取新的token;
34                 如果读到其它字符则解析失败
35             */
36         }
37         if (last_space) {  //表示一个token开始了,第一个非space字符
38             /*
39                 忽略掉space字符;
40                 如果字符是';','{','}',此次命令读取结束;
41                 如果字符是'#',字符后面是注释;
42                 如果字符是'\',下一个字符将被忽略
43                 如果字符是'"',''',下一个字符在单引号或双引号中
44             */
45         } else {  //开始读取新token
46            /*
47                 如果字符是'$',后面的非space字符组成变量token;
48                 碰到了结束引号'"','''时,token读取完成;
49                 碰到了space字符,token读取完成
50            */
51            if (found){
52                 /*
53                     将找到的token追加到cf->args数组中,并且每个token字符串以'\0'结束
54                 */
55            }
56         }
57     }
58 }

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

 1 static ngx_int_t
 2 ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
 3 {
 4     ...
 5     for (i = 0; ngx_modules[i]; i++) {
 6         cmd = ngx_modules[i]->commands;
 7         ...
 8         for ( /* void */ ; cmd->name.len; cmd++) {
 9             //命令名称对比
10             //模块类型对比
11             //命令类型对比
12             //命令是否带块
13             //检测命令参数
14             conf = NULL;
15             if (cmd->type & NGX_DIRECT_CONF) {
16                 conf = ((void **) cf->ctx)[ngx_modules[i]->index];
17
18             } else if (cmd->type & NGX_MAIN_CONF) {
19                 conf = &(((void **) cf->ctx)[ngx_modules[i]->index]);
20
21             } else if (cf->ctx) {
22                 confp = *(void **) ((char *) cf->ctx + cmd->conf);
23
24                 if (confp) {
25                     conf = confp[ngx_modules[i]->ctx_index];
26                 }
27             }
28             //调用命令中的set函数
29             rv = cmd->set(cf, cmd, conf);
30             ...
31         }
32     }
33     ...
34 }

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

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

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

1 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指令,它们在全局作用域中并未被分配空间,所以在指令处理函数中分配的空间需要挂接到全局作用域中,故传递给指令处理函数的参数是全局作用域的地址。

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

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

1 confp = *(void **) ((char *) cf->ctx + cmd->conf);
2 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模块很简单,它的定义如下:

 1 static ngx_command_t  ngx_http_commands[] = {
 2     { ngx_string("http"),  //http命令描述结构
 3       NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
 4       ngx_http_block,
 5       0,
 6       0,
 7       NULL },
 8       ngx_null_command
 9 };
10 static ngx_core_module_t  ngx_http_module_ctx = {
11     ngx_string("http"),
12     NULL,
13     NULL
14 };
15 ngx_module_t  ngx_http_module = {
16     NGX_MODULE_V1,
17     &ngx_http_module_ctx,                  /* module context */
18     ngx_http_commands,                     /* module directives */
19     NGX_CORE_MODULE,                       /* module type */
20     ...
21 };

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

 1 static char *
 2 ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 3 {
 4     ...
 5     ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));  //http main配置结构
 6     ...
 7     *(ngx_http_conf_ctx_t **) conf = ctx;  //将http main配置结构挂到全局作用域上
 8     //计算NGX_HTTP_MODULE类型模块的个数,并计算每个NGX_HTTP_MODULE模块在全部NGX_HTTP_MODULE模块中的下标
 9     ngx_http_max_module = 0;
10     for (m = 0; ngx_modules[m]; m++) {
11         if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
12             continue;
13         }
14         ngx_modules[m]->ctx_index = ngx_http_max_module++;
15     }
16     //http的main域配置
17     ctx->main_conf = ngx_pcalloc(cf->pool,
18                                  sizeof(void *) * ngx_http_max_module);
19     ...
20     //http的server域,用来合并server块中的域
21     ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
22     ...
23     //http的local域,用来合并server块中的local块中的域
24     ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
25     //创建各个作用域对应的配置结构
26     for (m = 0; ngx_modules[m]; m++) {
27         if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
28             continue;
29         }
30         module = ngx_modules[m]->ctx;
31         mi = ngx_modules[m]->ctx_index;
32         if (module->create_main_conf) {
33             ctx->main_conf[mi] = module->create_main_conf(cf);
34             ...
35         }
36         if (module->create_srv_conf) {
37             ctx->srv_conf[mi] = module->create_srv_conf(cf);
38             ...
39         }
40         if (module->create_loc_conf) {
41             ctx->loc_conf[mi] = module->create_loc_conf(cf);
42             ...
43         }
44     }
45     pcf = *cf;
46     cf->ctx = ctx;  //更新配置上下文为http的上下文
47     ...
48     //递归ngx_conf_parse来调用处理http包含的块的配置信息
49     cf->module_type = NGX_HTTP_MODULE;  //模块类型为NGX_HTTP_MODULE
50     cf->cmd_type = NGX_HTTP_MAIN_CONF;  //指令的作用域
51     rv = ngx_conf_parse(cf, NULL);
52     ...
53     *cf = pcf;  //恢复配置上下文
54     ...
55 }

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

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

 1 static ngx_command_t  ngx_http_core_commands[] = {
 2     ...
 3     { ngx_string("server"),  //server指令
 4       NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
 5       ngx_http_core_server,
 6       0,
 7       0,
 8       NULL },
 9     { ngx_string("location"),  //location指令
10       NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
11       ngx_http_core_location,
12       NGX_HTTP_SRV_CONF_OFFSET,
13       0,
14       NULL },
15     ...
16 }
17
18 static ngx_http_module_t  ngx_http_core_module_ctx = {
19     ngx_http_core_preconfiguration,        /* preconfiguration */
20     NULL,                                  /* postconfiguration */
21     ngx_http_core_create_main_conf,        /* create main configuration */
22     ngx_http_core_init_main_conf,          /* init main configuration */
23     ngx_http_core_create_srv_conf,         /* create server configuration */
24     ngx_http_core_merge_srv_conf,          /* merge server configuration */
25     ngx_http_core_create_loc_conf,         /* create location configuration */
26     ngx_http_core_merge_loc_conf           /* merge location configuration */
27 };
28 ngx_module_t  ngx_http_core_module = {
29     NGX_MODULE_V1,
30     &ngx_http_core_module_ctx,             /* module context */
31     ngx_http_core_commands,                /* module directives */
32     NGX_HTTP_MODULE,                       /* module type */
33     ...
34 };

  这个模块定义了很多指令,其中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块中。

转载于:https://www.cnblogs.com/chengxuyuancc/p/3792258.html

nginx源码分析之模块初始化相关推荐

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

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

  2. Nginx 源码分析

    1.工程 ngx_conf_file.c ngx_connection.c ngx_cycle.c ngx_file.h ngx_module.c ngx_open_file_cache.h ngx_ ...

  3. Nginx源码分析:epoll事件处理模块概述

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> 事件处理模块概述 Nginx的高效请求的处理依赖于事件管理机制,本次默认的场景是Linux操 ...

  4. Nginx 源码分析-- 模块module 解析执行 nginx.conf 配置文件流程分析 一

    搭建nginx服务器时,主要的配置文件 nginx.conf 是部署和维护服务器人员经常要使用到的文件, 里面进行了许多服务器参数的设置.那么nginx 以模块 module为骨架的设计下是如何运用模 ...

  5. nginx源码分析(5)——监听socket初始化

    在nginx源码分析(4)中,看到了nginx的事件模型,但其中没有介绍监听socket的初始化.而对于web server来说,需要通过监听socket来监听客户端的连接等.本篇将会具体介绍这方面的 ...

  6. Nginx源码分析:惊群处理与负载均衡

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> Nginx的惊群处理与负载均衡概述 当Nginx工作在master/worker模式下时,就 ...

  7. Nginx源码分析:核心数据结构ngx_cycle_t与内存池概述

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> 核心数据结构与内存池概述 在Nginx中的核心数据结构就是ngx_cycle_t结构,在初始 ...

  8. Nginx源码分析:master/worker工作流程概述

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> Nginx的master与worker工作模式 在生成环境中的Nginx启动模式基本都是以m ...

  9. Nginx源码分析:启动流程

    nginx源码分析 nginx-1.11.1 参考书籍<深入理解nginx模块开发与架构解析> nginx简介 Nginx的作为服务端软件,表现的主要特点是更快.高扩展.高可靠性.低内存消 ...

最新文章

  1. Linux中源码包的管理
  2. ngx_lua应用最佳实践
  3. SpringCloud教程- 路由网关Zuul (SpringCloud版本Greenwich.SR4)
  4. c语言 fscanf的头文件,fscanf函数在哪个头文件中
  5. SAP Fiori Elements List Report table 里的普通按钮,Global 按钮 和 Determining 按钮
  6. 基于ASP.NET Core SignalR的流式传输
  7. hystrix合并请求_Hystrix中的批处理(折叠)请求
  8. 酷炫,用Html5/CSS实现文字阴影
  9. 清华大学-美团数字生活联合研究院成立
  10. linux 终端 朗读,使Linux终端朗读文字的小技巧分享
  11. requestmapping中path与value区别_1、Spring注解之@RequestMapping
  12. 《数据结构》双链循环线性表
  13. 机构报告:大数据分析提升企业决策水平
  14. returned message 找不到可安装的 ISAM。.
  15. 深入浅出聊优化:从Draw Calls到GC(转)
  16. 从零开始的车牌识别课题设计(一)
  17. lisp方格网法计算土方量_CASS方格网法如何计算土方量
  18. Python办公系列--Python创建Excel工作簿
  19. 赛前采访里皮表示:希望队员用真心踢球。
  20. HDFS存储大量小文件居然有这样的问题!看我怎么搞定它!

热门文章

  1. 手动排除fbiytty和vcxlcph病毒的干扰
  2. value proposition canvas
  3. rejection from Cambridge Machine Learning and Machine Intelligence MPhil
  4. kmp oj 亲和串
  5. Django入门项目实践(中)
  6. Shell脚本入门基础
  7. 并发数据结构-1.1.2 阻塞技术
  8. kubernetes 1.5 + nginx负载均衡 + jenkins集群 实战(一)
  9. cometD离线消息
  10. Incorrect string value: '\xF0\x90\x8D\x83...' for column 通用解决方案