转载自:http://www.cnblogs.com/jzhlin/archive/2012/06/18/nginx_conf_1.html
Nginx 源码分析-- 模块module 解析执行 nginx.conf 配置文件流程分析 一
搭建nginx服务器时,主要的配置文件 nginx.conf 是部署和维护服务器人员经常要使用到的文件, 里面进行了许多服务器参数的设置。那么nginx 以模块 module为骨架的设计下是如何运用模块 module来解析并执行nginx.conf配置文件下的指令的呢?在探究源码之前,需要对nginx下的模块 module 有个基本的认知(详情参考前面的博文 Nginx 源码分析-- 浅谈对模块module 的基本认知 )同时也要对nginx中常用到的一些结构有个基本的了解如: 内存池pool 管理相关的函数、ngx_string 的基本结构等(详情参考前面的博文),若不然看代码的时候可能不能很明晰其中的意思,本文着重探究的是解析执行的流程。
1、从main函数说起。
Nginx的main函数在nginx.c文件中(本文使用release-1.3.0版本源码 ,200行),因为是主函数其中涉及到了许许多多的功能模块的初始化等内容,我们只关注我们需要的部分。看到326行:
ngx_max_module = 0; for (i = 0; ngx_modules[i]; i++) { ngx_modules[i]->index = ngx_max_module++; }
cycle = ngx_init_cycle(&init_cycle);
可以看出来,这里对 ngx_modules ( Nginx 源码分析-- 浅谈对模块module 的基本认知 中有介绍)进行了索引编号,并且计算得到模块的总数 ngx_max_module。然后,对cycle进行初始化,跳转到 ngx_init_cycle中。对于cycle 这个变量是nginx的核心变量,可以说模块机制都是围绕它进行的,里面的参数比较复杂涉及到的内容十分多,本文并不详细对它讨论,可以将其看作是一个核心资源库。
2、ngx_init_cycle 函数
这个函数在文件ngx_cycle.c中(43行),这个函数是nginx初始化中最重要的函数之一,里面涉及到与cycle变量相关的初始化工作,看到第188行
cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
这里获取了 ngx_max_module 个指针空间,用来保存每个模块的配置信息,从cycle 变量的字段conf_ctx 命名中就可以知道,ctx 为context 上下文的缩写。接下来看到,下面这段:
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; } }
意思就是获取模块中属于 NGX_CORE_MODULE 类的模块,如果需要创建配置信息就创建相应的配置信息,并且将地址保存在先前创建好的 cycle->conf_ctx 地址空间中,完成核心模块配置文件的创建过程,至此前期工作就基本完成了。
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; }
前面conf的赋值那段,无非是对conf进行些必要的初始化。注意一下这里解析的都是对核心模块进行的,创建的配置文件也只是核心模块。关键的函数开始出现了:ngx_conf_param(&conf) 将conf需要的参数(可能没有就是空)存到conf中,ngx_conf_parse(&conf, &cycle->conf_file) 解析配置文件!
3、函数ngx_conf_parse 指令解析函数,关键函数!
char * ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename) { char *rv; ngx_fd_t fd; ngx_int_t rc; ngx_buf_t buf; ngx_conf_file_t *prev, conf_file; enum { parse_file = 0, parse_block, parse_param } type; /*
该函数存在三种运行方式,并非一定需要打开配置文件
*/ #if (NGX_SUPPRESS_WARN) fd = NGX_INVALID_FILE; prev = NULL; #endif /*
filename 的值为 nginx.conf 的路径
*/ if (filename) { /* 打开配置文件 */ fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); ...
/*
保存cf->conf_file 的上文
*/ prev = cf->conf_file;
/*
定义cf->conf_file 当前的变量信息
*/
cf->conf_file = &conf_file; /*
接下来是对,conf_file 的参数进行设置,为了方便阅读省略此处代码
*/
...
/*
将函数的运行模式定位为 parse_file ,配置文件模式。
*/
type = parse_file;
/*
其它两个else 是定义其他模式,在解析nginx.conf时并不会使用到
*/ } else if (cf->conf_file->file.fd != NGX_INVALID_FILE) { type = parse_block; } else { type = parse_param; } /*
完成对配置文件信息的,初步设置之后,就开始对配置文件进行解析。
*/
for ( ;; ) {
/*
获取从配置文件nginx.conf中读取的指令名,对于 ngx_conf_read_token 下面给出来返回参数的详细英文注释
*/
rc = ngx_conf_read_token(cf); /* * ngx_conf_read_token() may return * * NGX_ERROR there is error * NGX_OK the token terminated by ";" was found * NGX_CONF_BLOCK_START the token terminated by "{" was found * NGX_CONF_BLOCK_DONE the "}" was found * NGX_CONF_FILE_DONE the configuration file is done */
/*
如果错误,调转到done处执行
*/ if (rc == NGX_ERROR) { goto done; }
/*
如果如到“}”符号,跳转到done处执行,出现错误跳到failed处
*/ if (rc == NGX_CONF_BLOCK_DONE) { if (type != parse_block) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\""); goto failed; } goto done; }
/*
如果配置文件全部解析完成,调转到done处执行。
*/ if (rc == NGX_CONF_FILE_DONE) { if (type == parse_block) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected end of file, expecting \"}\""); goto failed; } goto done; }
/*
如果遇到“{"但出现错误,调转到failed 处执行
*/ if (rc == NGX_CONF_BLOCK_START) { if (type == parse_param) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "block directives are not supported " "in -g option"); goto failed; } } /*
前面对可能出现的情况都进行了相应的跳转,那么剩下的就是读取 指令正确后执行的过程了,主要分为两种,一种为NGX_OK 一般指令的进行如:worker_processes
另一种 NGX_CONF_BLOCK_START 就是以{作为结束符指令的执行,如:events、http 这类有二级指令的。
rc == NGX_OK || rc == NGX_CONF_BLOCK_START
*/ if (cf->handler) { /*
指令执行前是否要进行些处理工作 * the custom handler, i.e., that is used in the http's * "types { ... }" directive */
rv = (*cf->handler)(cf, NULL, cf->handler_conf); if (rv == NGX_CONF_OK) { continue; } if (rv == NGX_CONF_ERROR) { goto failed; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, rv); goto failed; } /*
下一个关键函数 ngx_conf_handler
*/
rc = ngx_conf_handler(cf, rc); if (rc == NGX_ERROR) { goto failed; } } failed: rc = NGX_ERROR; done: /*
一些完成后的处理,释放资源或者 出错处理。省略
*/ ...
/*
恢复上下文
*/
cf->conf_file = prev; } if (rc == NGX_ERROR) { return NGX_CONF_ERROR; } return NGX_CONF_OK; }
在以上代码中,除了将关键函数用红色标记以外,还特意将 函数中 对上下文的保存和还原 工作的地方进行了红色标记,因为在nginx源码中经常使用到这种机制,可以记住下这样的写法。
4、函数ngx_conf_handler 指令处理函数,关键函数!
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last) { ...
for (i = 0; ngx_modules[i]; i++) { /* 查找与指令想对应的模块 module*/ if (ngx_modules[i]->type != NGX_CONF_MODULE && ngx_modules[i]->type != cf->module_type) { continue; } /*
读取模块的指令集
*/
cmd = ngx_modules[i]->commands; if (cmd == NULL) { continue; } for ( /* void */ ; cmd->name.len; cmd++) { /*
遍历指令集中的指令,并找寻 从配置文件中读取到的 指令相对应的 内容
*/
if (name->len != cmd->name.len) { continue; } if (ngx_strcmp(name->data, cmd->name.data) != 0) { continue; } /* 判断下指令类型 是否正确*/ if (!(cmd->type & cf->cmd_type)) { if (cmd->type & NGX_CONF_MULTI) { multi = 1; continue; } goto not_allowed; }
...
/*判断指令参数是否正确*/ if (!(cmd->type & NGX_CONF_ANY)) { if (cmd->type & NGX_CONF_FLAG) { if (cf->args->nelts != 2) { goto invalid; } } else if (cmd->type & NGX_CONF_1MORE) {
}
...
} /*
通过指令的类型,来设置执行指令时需要的 模块前期创建的 cf_ctx里面的配置信息,朔源就是 cycle->conf_ctx 当然它指向的 上下文 可能已经发生了改变
*/
conf = NULL;
if (cmd->type & NGX_DIRECT_CONF) { conf = ((void **) cf->ctx)[ngx_modules[i]->index]; }
...
/*
执行指令对应的 功能函数!!
*/ rv = cmd->set(cf, cmd, conf);
/*
如果执行成功,返回 成功。
*/
if (rv == NGX_CONF_OK) {
return NGX_OK;
}
/*
至此,配置文件的指令执行就结束了。后面都是一些出错处理,在此省略。
*/ ... } } ... }
Nginx 源码分析-- 模块module 解析执行 nginx.conf 配置文件流程分析 二
接上一篇,通过关键代码段的分析、和一些关键函数的摘录对nginx 运用module 模块构架对nginx.conf 进行解析执行的流程有了一个基本认识。流程总结如下:
1、获取全部参与编译的模块module 进行统计编号。
2、根据module 模块的个数分配 配置信息资源的指针空间。
3、创建NGX_CORE_MODULE 核心模块的配置信息,并将指针空间中对应模块编号的指针进行设置。
4、初始化conf 配置信息,解析参数 ngx_conf_param ,解析配置文件 ngx_conf_parse。
以上第4步中解析配置文件 ngx_conf_parse 过程又可分为:
1、获取配置文件。
2、保存当前配置文件的上下文,并将cf->conf_file 指向当前配置文件。
3、读取当前配置文件中的配置指令名 ngx_conf_read_token 。
4、判断读取指令的类别、是否正确。
5、执行指令前是否进行其他处理。
6、交给 ngx_conf_handler 处理指令。
7、全部执行完后,恢复上下文。
以上第6步 ngx_conf_handler 过程又可分解为:
1、查找与配置信息中指定分析模块的类别,并获取该模块的指令集。
2、遍历指令集是否有要求处理的指令。
3、如果匹配,判断指令类型、指令参数是否正确。
4、执行指令对应的功能函数 set。
5、正确完成返回NGX_OK。
整个流程一环扣一环,利用其中的 cf(ngx_conf_t) 这个结构起到了穿针引线、传递资源的作用。分析到这里,NGX_CORE_MODULE中的指令是知道如何执行的了,但是像https{ … } 、events{ … }括号中的指令是如何执行的呢?它可并非属于核心模块里面的指令。我们这里以events模块进行分析。
我们首先来看events 核心指令的一些参数
static ngx_command_t ngx_events_commands[] = { { ngx_string("events"), NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_events_block, 0, 0, NULL }, ngx_null_command };
里面的 NGX_CONF_BLOCK 有没有印象?想想 ngx_conf_read_token() 里面返回的参数有NGX_CONF_BLOCK_START 、NGX_CONF_BLOCK_DONE 再联系下ngx_conf_handler 里面的这句:
if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) { … }
明白了吧,这个参数的意识就代表了,它是二级模块的标示,他后面跟着的是 { 这个符号作为结束符。这个指令参数也告诉我们,解析到events 这个标识 时调用 ngx_events_block。那就来看看 ngx_events_block 吧。
static char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *rv; void ***ctx; ngx_uint_t i; ngx_conf_t pcf; ngx_event_module_t *m; /*统计 envent 模块总数,并建立编号*/ /* 感觉相似么? 上一篇中的 main 函数里的那段代码!! */
ngx_event_max_module = 0; for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_EVENT_MODULE) { continue; } ngx_modules[i]->ctx_index = ngx_event_max_module++; } ctx = ngx_pcalloc(cf->pool, sizeof(void *)); if (ctx == NULL) { return NGX_CONF_ERROR; }
/* 上一篇又有相似的, ngx_init_cycle 函数 里的那句!! */ *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
if (*ctx == NULL) { return NGX_CONF_ERROR; } *(void **) conf = ctx;
/* 还是 ngx_init_cycle 函数 里有相似的!!! */
for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_EVENT_MODULE) { continue; } m = ngx_modules[i]->ctx; if (m->create_conf) { (*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle); if ((*ctx)[ngx_modules[i]->ctx_index] == NULL) { return NGX_CONF_ERROR; } } }
/* 保存原来的cf ,设置当前的cf ,这样 cf 就变成二级模块的了!!不再是核心模块了!*/ pcf = *cf; cf->ctx = ctx; cf->module_type = NGX_EVENT_MODULE; cf->cmd_type = NGX_EVENT_CONF;
/* 解析二级模块指令了!用的还是 ngx_conf_parse(上一篇) ,注意 cf 变了上面重新设置了!*/
rv = ngx_conf_parse(cf, NULL);
/* 恢复cf */ *cf = pcf; if (rv != NGX_CONF_OK) return rv;
/* 初始化,ngx_init_cycle 函数 里也有类似的。*/
for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->type != NGX_EVENT_MODULE) { continue; } m = ngx_modules[i]->ctx; if (m->init_conf) { rv = m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]); if (rv != NGX_CONF_OK) { return rv; } } } return NGX_CONF_OK; }