nginx在启动,reload和平滑升级时,都会重新加载配置。重新加载配置分成两步:
1. 重新加载命令行参数(-g)中定义的全局配置
2. 重新加载配置文件中的配置
解析配置的核心函数是ngx_conf_parse,不论是解析命令行定义的全局配置还是解析配置文件内容,最终都会调用该函数。函数的原型如下:
char *ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename);
参数说明:
cf::配置解析上下文。在函数执行前由调用者设定
filename:配置文件路径。函数会根据该参数是否为NULL决定是否打开该文件并设置相关文件及buf参数,为后面读取配置做准备
ngx_conf_parse的一次执行流程可以归纳成几个步骤:
设置解析上下文环境
nginx中,调用到ngx_conf_parse的场景有很多。在刚开始读取一个配置文件,遇到一个块指令时都会调用到,因此需要根据不同的场景设置上下文,为后续的配置解析和保存提供信息
打开配置文件
在filename不为NULL的时候才需要
设置解析类型
根据上下文环境和filename参数设置解析类型。解析类型一共有三种,parse_file,parse_block和parse_param,分别对应于解析配置文件,解析配置块和解析命令行参数
读取配置
读取配置是一个循环的过程。每一轮是以调用ngx_conf_read_token函数开始的。该函数的作用是以字节为单位读取并分析配置,根据预定的语法规则判断当前语法状态,决定返回值,并更新cf中的缓冲区位置,以便下次读取。如果是合法的指令格式,读取的参数将会保存在cf->args中,为后面指令的set回调准备数据
语法检查
根据ngx_conf_read_token的返回值判断配置语法是否合法,并决定下一步行为。如果判定语法有错误,就直接打印错误信息,然后结束本次执行;如果语法没有问题,就执行ngx_conf_handler函数,查找并执行读取到的指令。如果ngx_conf_handler返回NGX_ERROR,立即结束本次流程,否则继续下一轮循环
执行自定义处理函数
如果当前上下文制定了自定义处理函数,那么就执行自定义处理函数,然后继续下一轮,否则跳到下一步
执行指令
ngx_conf_handler是真正执行指令的地方。ngx_conf_handler会遍历所有模块的指令,查找与读取到的指令名称和上下文环境匹配的模块指令。如果找到匹配指令,就根据指令设置的类型和上下文位置查找到合适的上下文,将该上下文作为指令的set回调函数的第三个参数,调用指令的set回调函数,并根据set回调函数的返回值决定返回值。如果没有匹配的指令,将会打印错误信息,然后返回NGX_ERROR
看完了ngx_conf_parse的执行流程,再来看一下一次完整的配置文件加载过程。nginx的配置文件加载在ngx_init_cycle中进行。下面是从ngx_init_cycle中摘取的相关代码。
ngx_memzero(&conf, sizeof(ngx_conf_t));
/* STUB: init array ? */
conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
if (conf.args == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
if (conf.temp_pool == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
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
/* 先解析并执行命令行参数(-g选项)中定义的全局配置 */
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;
}
struct ngx_conf_s {
char *name;//未使用
/*一次循环读取到的配置都保存在args中,args[0]是指令名称,args[1]是指令第一个参数,args[2]是第二次参数,依次类推。在下一次循环开始时,args会被清空,以便保存下次循环读取的配置 */
ngx_array_t *args;
ngx_cycle_t *cycle;cf所属cycle
ngx_pool_t *pool;//cf所使用的内存池,与cycle共用一个
ngx_pool_t *temp_pool;//cf所使用的临时内存池,配置加载完成后会被销毁
ngx_conf_file_t *conf_file;//配置文件路径
ngx_log_t *log;//日志对象
void *ctx;//当前所处的上下文环境
ngx_uint_t module_type;//当前上下文环境允许的模块类型
ngx_uint_t cmd_type;//当前上下文环境类型
ngx_conf_handler_pt handler;//自定义配置处理函数
char *handler_conf;//自定义配置处理函数的参数
};
了解了cf之后,现在可以开始分析nginx的配置加载过程了。在开始加载配置前,nginx指定了开始加载配置的上下文,如下所示:
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;
ctx,module_type和cmd_type是重点,其它三个与我们的分析关系不大,可以略过。
ngx_conf_t pcf;//用来临时保存上下文环境
ngx_http_conf_ctx_t *ctx;//http块的上下文环境
...
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
*(ngx_http_conf_ctx_t **) conf = ctx;
...
pcf = *cf;/*保存调用前上下文环境*/
...
/* 设置当前上下文环境是http块 */
cf->ctx = ctx;
...
cf->module_type = NGX_HTTP_MODULE;//当前上下文环境允许的模块类型是NGX_HTTP_MODULE
cf->cmd_type = NGX_HTTP_MAIN_CONF;//当前上下文环境是http全局
rv = ngx_conf_parse(cf, NULL);
...
*cf = pcf;/*还原调用前上下文*/
return rv;
上面的代码很清晰地展示了http指令的逻辑,这与我们的描述是一致的。
另外,指令块内如果还有属于该指令块的指令块(例如http内的server, location等),这些指令块的回调函数也将执行类似的逻辑。最终,在遇到文件结束符时,配置加载就完成了。