已经看了一段时间的nginx的源代码了,但其实启动的过程一直感觉弄得不是很清楚,因为自己一直在回避配置文件的解析过程,以及具体是如何根据配置文件中的命令来回调相应模块所定义的回调函数。如果这个不弄清楚的话,其实很多地方都容易一知半解。好吧,这次决定把这部分弄清楚。
首先我们先看看模块的命令的定义:
//模块的命令定义
struct ngx_command_s {
ngx_str_t name; //命令的名字
ngx_uint_t type; //命令的类型
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); //命令的回调函数,cf为配置结构,cmd为命令,conf为相应模块的配置结构
ngx_uint_t conf;
ngx_uint_t offset; //这个域是用来当该命令是用来设置模块配置结构的某个属性的时候用的,可以用这个偏移在模块中迅速的定位相应的苏醒
void *post;
};
其中name为这个命令的名字,type是这个名字的类型,set函数是对应的命令的回调函数。cf为传进来的配置结构,这个待会说,cmd为当前命令,conf为该命令所在模块的配置结构。
接下来我们来看看配置结构的定义吧:
//配置结构的定义
struct ngx_conf_s {
char *name; //名字
ngx_array_t *args; //参数的数组,解析出来的命令或者参数都会放入到这个数组当中去
ngx_cycle_t *cycle; //对应的cycle变量
ngx_pool_t *pool; //对应的内存池
ngx_pool_t *temp_pool; //临时内存池
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;
};
name为这个配置的名字,args域是一个数组,将会用来保存解析出来的命令和参数,例如解析命令如下:
worker_processes 1;
那么args[0]将会保存的是worker_porcess,args[1]将会保存的是1。conf_file是当前配置结构所对应的配置文件,这个比较重要的。然后就还有ctx是一些配置结构的上下文,module用来指明需要解析某种类型的模块,cmd_type指明解析的命令类型。嗯,基本比较重要的就是这些了。
接下来看配置文件的定义:
typedef struct {
ngx_file_t file; //该配置文件对应的文件
ngx_buf_t *buffer; //缓存
ngx_uint_t line; //第某行
} ngx_conf_file_t;//配置文件的定义
这里file为对应的文件,buffer为缓存,我们在解析的时候实际上是在buffer中读取的。
好了,接下来我们来看看ngx_core_module模块的命令解析吧,还是用这个命令:worker_processes 1;在重要的init_cycle函数中会用如下代码来解析配置文件:
//对配置结构的一些初始化
conf.ctx = cycle->conf_ctx; //设置配置结构的上下文
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE; //设置需要解析core类型模块的配置,
conf.cmd_type = NGX_MAIN_CONF; //需要解析的命令的类型
//ngx_conf_parse函数非常重要,这里用来解析配置文件,第二个参数传进来了配置文件的命令
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
ngx_conf_parse函数非常重要,这里传进去的第一个参数是配置结构,第二个参数是配置文件的路径,在调用该函数之前还有对配置结构进行初始化的部分,例如本次解析对应的模块式NGX_CORE_MODULE类型的。
好了,接下来我们开始了解ngx_conf_parse函数(Ngx_conf_file.c):
char *
ngx_conf_param(ngx_conf_t *cf)
{
char *rv;
ngx_str_t *param;
ngx_buf_t b;
ngx_conf_file_t conf_file;
param = &cf->cycle->conf_param;
if (param->len == 0) {
return NGX_CONF_OK;
}
ngx_memzero(&conf_file, sizeof(ngx_conf_file_t));
ngx_memzero(&b, sizeof(ngx_buf_t));
b.start = param->data;
b.pos = param->data;
b.last = param->data + param->len;
b.end = b.last;
b.temporary = 1;
//初始化配置文件结构
conf_file.file.fd = NGX_INVALID_FILE; //表示当前配置文件的文件描述符是非法的
conf_file.file.name.data = NULL;
conf_file.line = 0; //第零行
cf->conf_file = &conf_file; //为配置结构的配置文件域赋值
cf->conf_file->buffer = &b; //为配置文件的缓存复制
rv = ngx_conf_parse(cf, NULL);
cf->conf_file = NULL;
return rv;
}
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
//如果文件名提供了,在第一次解析配置文件的时候会传入配置文件的路径
if (filename) {
/* open configuration file */
//打开文件
fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
if (fd == NGX_INVALID_FILE) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
ngx_open_file_n " \"%s\" failed",
filename->data);
return NGX_CONF_ERROR;
}
prev = cf->conf_file; //保存以前的配置文件
cf->conf_file = &conf_file; //相当于是指向一个空的配置文件结构
if (ngx_fd_info(fd, &cf->conf_file->file.info) == -1) { //判断刚刚打开的文件描述符是否有效,并将文件的一些基本信息保存起来
ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
ngx_fd_info_n " \"%s\" failed", filename->data);
}
cf->conf_file->buffer = &buf; //相当于是为结构的配置文件结构赋空的buffer
buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log); //相当于是buffer分配内存,4kb
if (buf.start == NULL) {
goto failed;
}
//初始化buffer结构,因为解析配置的话是直接在buffer中读取数据的
buf.pos = buf.start; //pos表示当前读取的地方
buf.last = buf.start; //last表示当前buffer中最后一个字节的位置
buf.end = buf.last + NGX_CONF_BUFFER; //buffer的end指针
buf.temporary = 1;
//初始化配置文件结构
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 = 0; //初始化偏移为0
cf->conf_file->file.log = cf->log;
cf->conf_file->line = 1;
type = parse_file; //表示是解析文件
} else if (cf->conf_file->file.fd != NGX_INVALID_FILE) {
type = parse_block; //表示解析块命令
} else {
type = parse_param; // 解析参数
}
for ( ;; ) {
//解析配置文件,并将解析出来的参数保存起来,保存到conf的args当中去,在开始的时候会将conf的args数组清零
rc = ngx_conf_read_token(cf);
//ngx_conf_read_token函数的主要功能是读取并解析配置文件,
//解析的参数存到cf->args中,读取到";"标点则返回
//读取到"{",则接着读取,读取到"}",这直接完成。
/*
* 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
*/
if (rc == NGX_ERROR) {
goto done;
}
if (rc == NGX_CONF_BLOCK_DONE) { //某一个命令快解析完毕
if (type != parse_block) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\"");
goto failed;
}
goto 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;
}
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;
}
}
/* rc == NGX_OK || rc == NGX_CONF_BLOCK_START */ //接下来可以用command相应的set回调函数来处理该命令或者可以开始处理块命令了
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;
}
rc = ngx_conf_handler(cf, rc); //找到相应命令的回调函数,并执行
if (rc == NGX_ERROR) {
goto failed;
}
}
failed:
rc = NGX_ERROR;
done:
if (filename) {
if (cf->conf_file->buffer->start) {
ngx_free(cf->conf_file->buffer->start);
}
if (ngx_close_file(fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
ngx_close_file_n " %s failed",
filename->data);
return NGX_CONF_ERROR;
}
cf->conf_file = prev;
}
if (rc == NGX_ERROR) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
嗯,该函数比较长,而且里面居然还有goto语句。第一次调用这个函数的时候会传入配置文件的路径,因而函数也会通过该路径来初始化配置结构cf的配置文件结构conf_file结构。然后进入循环,不断的调用ngx_conf_read_token函数来解析配置文件(每读完一句命令它都会返回一次),该函数具体的就不讲了,比较繁琐,但是可以说明它的意思。在该函数中首先将配置结构cf的args清零,然后开始从配置结构的配置文件结构的buffer中读取字符,然后根据;{},等符号来返回不同种的状态,嗯,其实上面的英文注释都会比较清楚了(一般情况下它都会一行一行的来读取命令,因为配置文件中大都是用一行写一句)。对于解析出来的命令,例如
worker_processes 1;那么它将在配置结构cf的args域中保存,args[0]保存的是worker_processes ,args[1]则保存的是1。然后返回到ngx_conf_parse函数后,会根据返回的状态来进行相应的动作,其实最重要的一句代码是:
rc = ngx_conf_handler(cf, rc); //找到相应命令的回调函数,并执行
调用ngx_conf_handler函数来处理解析出来的命令。
好了,接下来可以看ngx_conf_handler:
//该函数用于找到了该命令对应的回调函数,并执行
static ngx_int_t
ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
char *rv;
void *conf, **confp;
ngx_uint_t i, found;
ngx_str_t *name;
ngx_command_t *cmd;
name = cf->args->elts; //获取刚刚解析出来的命令
found = 0;
//遍历所有的模块,用他们的命令与刚刚解析出来的命令比较
for (i = 0; ngx_modules[i]; i++) {
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;
}
found = 1;
if (ngx_modules[i]->type != NGX_CONF_MODULE
&& ngx_modules[i]->type != cf->module_type)
{
continue; //要找到这次配置结构指定的模块类型
}
/* is the directive's location right ? */
if (!(cmd->type & cf->cmd_type)) { //判断两个命令的类型是否相同
continue;
}
if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"directive \"%s\" is not terminated by \";\"",
name->data);
return NGX_ERROR;
}
if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"directive \"%s\" has no opening \"{\"",
name->data);
return NGX_ERROR;
}
/* is the directive's argument count right ? */
//这个用于判断当前命令的参数数目是否合格
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) { //一个参数
if (cf->args->nelts < 2) {
goto invalid;
}
} else if (cmd->type & NGX_CONF_2MORE) {
if (cf->args->nelts < 3) {
goto invalid;
}
} else if (cf->args->nelts > NGX_CONF_MAX_ARGS) {
goto invalid;
} else if (!(cmd->type & argument_number[cf->args->nelts - 1]))
{
goto invalid;
}
}
/* set up the directive's configuration context */
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];
}
}
rv = cmd->set(cf, cmd, conf); //调用该命令的set回调函数,参数分别为配置结构,命令,与该模块的配置
if (rv == NGX_CONF_OK) {
return NGX_OK;
}
if (rv == NGX_CONF_ERROR) {
return NGX_ERROR;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%s\" directive %s", name->data, rv);
return NGX_ERROR;
}
}
嗯,这段代码也是比较长的,但是相对比较简单,上面的注释也已经基本说清楚了。首先遍历所有的模块,然后模块的所有命令,通过命令的名字进行比较,找到相应的命令,当然这里还要比较模块的类型与传进来配置结构指定的模块类型是否相同,以及命令的类型是否与传进来的配置结构指定的命令类型相同,还有参数的数目是否合格。然后再调用该命令的set回调函数。对于
worker_processes 1;命令,它的定义如下:
{ ngx_string("worker_processes"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
0,
offsetof(ngx_core_conf_t, worker_processes),
NULL },
这是一个设置属性的回调函数,其偏移量是worker_processes在ngx_core_conf_t结构中的偏移。好了,我们来看看它的回调函数,这是一个比较通用的设置属性的回调函数:
//cf为传进来的配置结构,cmd为当前的命令,conf为对应模块的配置结构
char *
ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *p = conf;
ngx_int_t *np;
ngx_str_t *value;
ngx_conf_post_t *post;
//根据偏移找到要复制域的指针
np = (ngx_int_t *) (p + cmd->offset);
if (*np != NGX_CONF_UNSET) {
return "is duplicate";
}
value = cf->args->elts;
//甩value[1]来找到当前配置项对应的value
*np = ngx_atoi(value[1].data, value[1].len);// 把配置的value保存到当前模块配置的相应属性去
if (*np == NGX_ERROR) {
return "invalid number";
}
if (cmd->post) {
post = cmd->post;
return post->post_handler(cf, post, np);
}
return NGX_CONF_OK;
}
函数还是很简单的,通过命令的offset,来找到相应的属性,然后从配置结构args中保存的参数中读取args[1] 的值保存过去就行了,那么对于刚刚说的命令,说白了就是将
ngx_core_module模块的配置结构ngx_core_conf_t的worker_processes的值设置为1就行了。
嗯上面基本上就把这些命令的解析和回调函数的执行过程说明白了。
另外对于像如下这样的块命令:
events {
use epoll; #epoll是多路复用IO(I/O Multiplexing)中的一种方式,但是仅用于linux2.6以上内核,可以大大提高nginx的性能
worker_connections 1024;#单个后台worker process进程的最大并发链接数
# multi_accept on;
}
首先events对应的是ngx_events_module模块的命令,在该命令的回调函数中接着又会有如下代码:
pcf = *cf; //相当于是用一个新的配置结构,等到event块命令解析完成以后在恢复
cf->ctx = ctx; //这里的配置结构的上下文换成刚刚生成的event模块的上下文
cf->module_type = NGX_EVENT_MODULE; //需要解析event模块的配置
cf->cmd_type = NGX_EVENT_CONF; //命令的类型
//由于events是一个block指令,events域下还可以配置很多其他指令,
//比如之前提过的use等,现在开始解析events block中的指令,完成初始化工作。
rv = ngx_conf_parse(cf, NULL); //这里用于解析配置文件中events{}这个block的解析
它用来继续解析这个块命令,这里就是设计到event类型的模块的东西了。因此可以说Nginx在配置文件解析的这个过程实际上是一个递归的过程。
对于每种类型的模块,都会有一个core类型的模块与之对应,例如event模块就有ngx_events_module模块与之对应,其实一个core类型的模块,然后在这个core类型的模块中在通过对接下来命令的解析来完成其余event模块的初始化,event执行等等过程。
嗯,Nginx的配置文件解析基本上就是这样了。