Nginx源码研究五:NGINX的配置信息管理
配置信息是nginx系统重要的组成部分,配置信息的使用,实际上包含两层,一层是用户针对参数定义了值,例如下面nginx参数文件中的 keepalive_timeout 65,还有一部分是用户没有定义值,那么系统会考虑赋予一个初始值,例如被注销掉的 #worker_connections 1024,我们知道,nginx拥有非常多的模块,同时用户可以在此基础上开发自己的模块,那么整个系统的配置参数是怎么管理的呢?下面将做一下研究。
user root; worker_processes 1; error_log logs/error.log info; events { #worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 81; server_name 10.221.20.1751; root /home/yebin/nginx/html; location / { root html; index index.html index.htm; } location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi.conf; } } }
1、配置信息生成过程
nginx采用模块化管理,每一个模块有模块自己关注的配置信息,同时模块有各自模块的类型,我们可以参考一下nginx的核心模块 ngx_core_module 的数据结构
ngx_module_t ngx_core_module = { NGX_MODULE_V1, &ngx_core_module_ctx, /* module context */ //模块的配置信息 ngx_core_commands, /* module directives */ //模块的指令 NGX_CORE_MODULE, /* module type */ //模块的类型 NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING };
针对不同的模块类型,nginx来做管理,我们看一下nginx配置信息的加载流程
a、首先,nginx会给自己依赖的模块编号(nginx.c 中main 函数里面)
//给所有模块进行编号,其中ngx_modules模块是在 objs中的ngx_modules.c定义 ngx_max_module = 0; for (i = 0; ngx_modules[i]; i++) { ngx_modules[i]->index = ngx_max_module++; }
b、nginx的核心数据结构cycle做配置信息的第一层管理,首先,申请cycle的存储空间
pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log); //申请16K的内存池 if (pool == NULL) { return NULL; } pool->log = log; cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t));
c、nginx每个依赖模块都有自己的配置信息,这样会申请一个数组,存储指向各个模块配置信息的指针
cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *)); if (cycle->conf_ctx == NULL) { ngx_destroy_pool(pool); return NULL; }
d、 针对核心模块做配置项的生成工作。
for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_CORE_MODULE) { continue; } module = ngx_modules[i]->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; } }
e、分析配置文件(nginx.conf),完成生成配置项的设置工作
conf.ctx = cycle->conf_ctx; conf.cycle = cycle; conf.pool = pool; conf.log = log; conf.module_type = NGX_CORE_MODULE; conf.cmd_type = NGX_MAIN_CONF; //住配置文件 #if 0 log->log_level = NGX_LOG_DEBUG_ALL; #endif if (ngx_conf_param(&conf) != NGX_CONF_OK) { environ = senv; ngx_destroy_cycle_pools(&conf); return NULL; } if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) { environ = senv; ngx_destroy_cycle_pools(&conf); return NULL; }
g、初始化核心配置信息,对nginx.conf没有设置值的配置项赋默认值。
//初始化NGX_CORE_MODULE for (i = 0; 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; } } }
由配置信息的生成过程,我们给出图示来表述一下各个模块的配置信息的存储方式
2、模块配置信息的存储
简单描述一下下面的这张图
* 在pool内申请内存,存放cycle
* cycle的成员变量指针指向一段内存,这段内存存放整个模块数量的指针。
* 模块类型主要分成,核心模块(NGX_CORE_MODULE), 事件模块(NGX_EVENT_MODULE), http处理模块(NGX_HTTP_MODULE)等,其中核心模块主要是ngx_core_module, ngx_events_module, ngx_http_module等五个模块,下面在分析系统对配置文件的分析过程后再分开讲这三个模块的配置项加载。
上面这张图大体描述了配置信息的存储结构,实际上我们知道http{} 里面可以配置多个server,很明显上面的图没有描述清楚这种情况,那么在下面,针对多server的配置信息存储做了描述。
首先从上面的图我们可以看到,针对每一个NGX_HTTP_MODULE, 都存在三种配置信息,分别是 NGX_HTTP_MAIN_CONF, NGX_HTTP_SRV_CONF 和 NGX_HTTP_LOC_CONF.
其次,NGX_HTTP_MAIN_CONF 针对所有的sever都是一致的,其余两种随各个server不同而定制。可以参考下面的图
一个server中可以配置多个location,那么location的配置信息如何保存呢,下面图示可以看出,nginx在做文本分析的时候。一旦分析到location指令,则申请内存生成ctx变量,该变量有三个成员,其中的main_conf指向http{}下独有的一份main_conf,srv_conf指向该location归属的server的配置信息,同时生成一份loc_conf数组,保存各个模块中归属http模块,同时指令类型是NGX_HTTP_LOC_CONF的配置信息。
很明显,针对上图中只要分析到location都会生成一份ctx保存这个location的相关配置信息,关于http{}和server{}和location{}之间的关系如下图,他们之间配置信息的保存方式,实际上也符合下图的方式。
3、系统对模块配置文件的分析过程
第一部分的第e条,有一个对nginx.conf配置信息的分析过程,在这个过程中,会调用模块的成员 “指令( ngx_core_commands, /* module directives */ //模块的指令 )” 去设置前面生成的配置项,从简要的代码看一下过程
char * ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename) { ………… if (filename) { ………… } for ( ;; ) { rc = ngx_conf_read_token(cf); //分析配置文件 ………… rc = ngx_conf_handler(cf, rc); ……… }
根据指令的类型,实际上确定了参数存放的位置。
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last) { ………… for (i = 0; ngx_modules[i]; i++) { cmd = ngx_modules[i]->commands; ………… for ( /* void */ ; cmd->name.len; cmd++) { ………… 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]; } } ………… rv = cmd->set(cf, cmd, conf); //利用指令去设置配置信息 } } }
这里看一下 rv = cmd->set(cf, cmd, conf); cf和cmd参数不必解释,conf参数主要用来确定配置信息结构体存储的位置。我们从第二部分的图可以看出取配置信息的方式如下:
* 第一种,针对cmd的type是包含NGX_DIRECT_CONF类型;例如获取ngx_core_module的配置信息 ccf = cycle->conf_ctx[ngx_core_module->index];
* 第二种,针对cmd的type是包含NGX_MAIN_CONF类型,不包含NGX_DIRECT_CONF类型; 这种类型未发现有配置信息的存放;
* 第三种,针对cmd的type是包含NGX_EVENT_CONF类型,不包含一二两种类型;获取ngx_epoll_module的配置信息指针 ecf = (*cycle->conf_ctx[ngx_events_module.index]) [ngx_epoll_module.ctx_index];
* 第四种,针对cmd的type是包含NGX_HTTP_LOC_CONF_OFFSET、 NGX_HTTP_SRV_CONF_OFFSET、 NGX_HTTP_MAIN_CONF类型,不包含一二两种类型;例如 ngx_http_access_loc_conf_t *alcf = ngx_http_get_module_loc_conf(r, ngx_http_access_module);