nginx配置解析流程

        上一篇文章分析了nginx配置文件缓冲区的管理,接下来将详细分析nginx是如何解析配置文件的。包含模块上下文结构的创建、core核心模块的解析、event事件模块的解析、http模块的解析。

一、模块上下文结构创建

        nginx中的核心模块、事件模块、http模块、赋值均衡模块,反向代理模块等,每一个模块都有一个上下文结构。在解析nginx.conf配置文件时,会将解析出的命令保存到相应模块的上下文结构中。例如:worker_connections 1024;该配置项表示每一个work最大可以处理1024个客户端的连接。nginx在解析到这个配置向时,会将1024这个值保存到事件模块的上下文ngx_event_conf_t中的connections变量中。

        nginx有多少个模块,就会创建多少个模块上下文结构。在ngx_init_cycle函数中,会创建所有的模块上下文结构。保存到ngx_cycle_s的conf_ctx变量中,这个变量是一个指针数组。

ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle)
{
	//创建所有模块的配置上下文空间
    cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
}

        创建完各个模块的上下文后,内存布局如下:刚创建时,指针数组中的每一个元素都是一个空指针。

二、core核心模块解析

        master进程初始化过程会调用ngx_init_cycle函数初始化ngx_cycle_t结构。这个结构是一个全局结构,每一个进程都有这样一个单独的结构。其中conf_ctx成员是一个指针数组,存放了所有模块的上下文结构。在解析配置文件后,会将解析的结果存储到这个指针数组指向的相应位置。下面代码段将创建一个这样的模块 上下文指针数组。

ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle)
{
	//将每一个核心模块上下文create_conf的返回值存入到conf_ctx中
    for (i = 0; ngx_modules[i]; i++) 
	{
		//必须是核心模块,其它模块暂时不解析,留到ngx_conf_parse函数中解析
        if (ngx_modules[i]->type != NGX_CORE_MODULE) 
		{
            continue;
        }
		//目前只有ngx_core_module模块的上下文实现了ngx_core_module_create_conf方法
        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模块实现了create_conf方法。因此创建核心模块的上下文结构后,内存布局如下:

        其中cycle->conf_ctx指针数组下标为0的位置就是ngx_core_module核心模块所在的上下文位置,将该指针指向ngx_core_conf_t结构。而ngx_core_module_create_conf方法目前只是给这个结构的所有成员赋一个初值,真正解析则在ngx_conf_parse函数中进行。 在ngx_conf_parse函数解析core模块的配置项时,会将nginx.conf配置文件中所有核心模块所关心的配置项存放到ngx_core_conf_t上下文结构。以worker_process 4;配置项为例说明core模块的解析流程。

ngx_conf_parse

    --->ngx_conf_read_token

        首先函数ngx_conf_read_token会一个个检测字符,从而得到worker_processes  4;之后会将worker_processes存放到ngx_conf_s的args数组下标为0位置,  4存储到数组下标为1位置。最后根据worker_processes配置项,在所有模块中查找是否有模块实现了worker_processes配置项命令。如果找到,则调用该命令回调。

ngx_conf_parse

    --->ngx_conf_handler

//根据配置项查找所有模块,获取到命令,并执行命令
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
	//执行命令
    rv = cmd->set(cf, cmd, conf);
}	
        ngx_core_module核心模块的命令表中,存在worker_processes命令。从而调用命令回调ngx_conf_set_num_slot。

//core模块命令
static ngx_command_t  ngx_core_commands[] = 
{
    { 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 },
}
        而函数ngx_conf_set_num_slot将把解析到的配置值保存到ngx_core_conf_t成员变量worker_processes。

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);   //找到ngx_core_conf_t成员worker_processes位置

    value = cf->args->elts;   //解析后的命令,在这个例子中value[0] = worker_processes, value[1] = 4

    *np = ngx_atoi(value[1].data, value[1].len);  //将值保存到ngx_core_conf_t成员worker_processes

    return NGX_CONF_OK;
}
       其它核心模块的命令解析流程和这个例子基本上一样。读者可以分析下其它core模块命令的实现流程,后续nginx服务器处理客户端请求时,会使用到各种命令。不对命令解析有个了解,到时还真不知道这个命令是做什么的,以及如何解析的。

三、event事件模块解析

        经过core核心模块的分析,我们知道,查找到具体的命令后,最终会调用函数执行具体的命令。

//根据配置项查找所有模块,获取到命令,并执行命令
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
	//执行命令
    rv = cmd->set(cf, cmd, conf);
}
        而ngx_events_module事件模块的命令数组中有一个events配置项,命令回调方法为 ngx_event_block

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
};
        最终解析到"event {"配置项后,会调用ngx_event_block方法。

char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	*ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
}
        其中ngx_event_max_module表示有多少个事件模块, 该函数会开辟所有事件模块的上下文空间。

char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	//调用各个事件模块的create_conf方法,生成每个事件模块的上下文,存放在指针数组中的相应位置
    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);
        }
    }
}
       各个事件模块实现的create_conf方法只是创建了上下文结构,然后给成员赋初值,此时并还没有解析具体的事件模块的配置。例如ngx_event_core_module模块的create_conf方法为ngx_event_create_conf,函数将创建ngx_event_conf_t结构,并给成员赋初值。

static void * ngx_event_create_conf(ngx_cycle_t *cycle)
{
    ngx_event_conf_t  *ecf;
	//创建ngx_event_core_module模块的上下文件结构
    ecf = ngx_palloc(cycle->pool, sizeof(ngx_event_conf_t));
	//给各个成员赋初值
    ecf->connections = NGX_CONF_UNSET_UINT;
    ecf->use = NGX_CONF_UNSET_UINT;
    ecf->multi_accept = NGX_CONF_UNSET;
    ecf->accept_mutex = NGX_CONF_UNSET;
    ecf->accept_mutex_delay = NGX_CONF_UNSET_MSEC;
    return ecf;
}
        创建完所有事件模块的上下文后,内存布局如下


        接下来将开始解析事件模块,函数ngx_events_block会开始调用ngx_conf_parse递归解析。这个时候从解析main块进入解析event块。递归解析完事件块后,会返回继续解析main块。

char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	pcf = *cf;
    cf->ctx = ctx;
    cf->module_type = NGX_EVENT_MODULE;		//指定为事件模块,则只会解析事件模块命令
    cf->cmd_type = NGX_EVENT_CONF;			

	//递归调用,解析事件块配置
    rv = ngx_conf_parse(cf, NULL);
}
        下图是从解析main块进入解析event的流程,在event块解析完成后,会返回到上一层函数调用,继续解析剩余的main块。

        在递归调用进入到event块后,开始解析event事件模块。解析具体event块里的所有命令的流程与解析core核心模块的过程是一样的。这里就不在分析了。

        在从解析event块返回后,接下来会对未解析到的event事件命令赋一个初值。

char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	//调用各个事件模块的init_conf方法,对各个事件模块未赋值的上下文成员进行初始化
    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]);
        }
    }
}

四、http模块解析

        经过core核心模块的分析,我们知道。查找到具体的命令后,最终会调用函数执行具体的命令。

//根据配置项查找所有模块,获取到命令,并执行命令
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last)
{
	//执行命令
    rv = cmd->set(cf, cmd, conf);
}

        而ngx_http_module事件模块的命令数组中有一个http配置项,命令回调方法为ngx_http_block。最终解析到"http {"配置项后,会调用ngx_http_block方法。

//开始解析http块
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{

	//创建一个http块的ngx_http_conf_ctx_t结构
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));

	//开辟所有http模块的main_conf
    ctx->main_conf = ngx_pcalloc(cf->pool,sizeof(void *) * ngx_http_max_module);

	//开辟所有http模块的srv_conf
    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);

	//开辟所有http模块的loc_conf
    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
}
        其中ngx_http_max_module表示有多少个http模块, 对于每个main块,各个http模块对会有一个上下文结构,存放各模块关心的main结构配置。server块,loction块也是如此,各个http模块对会有一个上下文结构,存放各模块关心的server, loction结构配置。

        接下来对于main块,创建各个模块关心的上下文结构;对于server块,创建各个模块关心的上下文结构; 对于loction块,创建各个模块关心的上下文结构

//开始解析http块
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    for (m = 0; ngx_modules[m]; m++) 
	{
		//只处理http模块,其它模块忽略
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) 
		{
            continue;
        }
        module = ngx_modules[m]->ctx;		
		//该http模块在http类模块中的位置
        mi = ngx_modules[m]->ctx_index;	

		//创建main块结构
        if (module->create_main_conf) 
		{
            ctx->main_conf[mi] = module->create_main_conf(cf);
        }
		//创建server块结构
        if (module->create_srv_conf) 
		{
            ctx->srv_conf[mi] = module->create_srv_conf(cf);
        }
		//创建loc块结构
        if (module->create_loc_conf) 
		{
            ctx->loc_conf[mi] = module->create_loc_conf(cf);
        }
    }
}
        同样,create_main_conf, create_srv_conf,  create_loc_conf只是创建了模块的上下文结构,然后给成员赋初值。此时还没有解析具体的http模块的配置,稍后会进行解析具体的http配置信息。创建http模块上下文结构后的内存布局如下:


         接下来将开始解析http模块,函数ngx_http_block会开始调用ngx_conf_parse递归解析。这个时候从解析main块进入解析http块。递归解析完http块后,会返回继续解析main块。

static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	//开始解析http块
    cf->module_type = NGX_HTTP_MODULE;  //只解析http块
    cf->cmd_type = NGX_HTTP_MAIN_CONF;
	//递归解析http块
    rv = ngx_conf_parse(cf, NULL);
}

        而在解析http块时,如果遇到"server  {"则会从http块递归进入到server块。递归解析完server块后,会返回继续解析http块。ngx_http_core_server函数负责解析server块。

static char * ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
	pcf = *cf;
    cf->ctx = ctx;
    cf->cmd_type = NGX_HTTP_SRV_CONF;  //只解析server块
	//开始解析server块
    rv = ngx_conf_parse(cf, NULL);
}

        而在解析server块时,如果遇到"location  {"则会从server块递归进入到location块。递归解析完location块后,会返回继续解析server块。函数ngx_http_core_location中负责解析loaction块

//解析location配置
static char * ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
{
	//开始解析local结构
    save = *cf;
    cf->ctx = ctx;
    cf->cmd_type = NGX_HTTP_LOC_CONF;
    rv = ngx_conf_parse(cf, NULL);
}
        文字描述起来比较吃力,比较难理解,还是上图吧!




五、配置解析源码分析       

        以上是分析nginx对配置文件的解析流程,现在来分析下解析代码的实现。而解析配置文件的过程还是比较复杂的,细节的东西还是留给读者吧,这里只是梳理下解析配置的框架代码,后续也会将详细注释的源码上传到github中。

        解析配置文件的入口为ngx_conf_parse,该函数会间接被递归调用,每次进入函数会触发三种状态中的一种。例如,首次调用函数时,将触发读取配置操作,这个时候状态为parse_file,开始打开文件,准备缓冲区。第二次调用时,则不需要指定filename,参数可以为空,表示已经打开过文件,开始处理复杂块。这个时候状态为parse_block,这种情况是在递归调用过程才会发生。例如解析http块时,会递归调用函数解析server块。parse_param参数只用于解析命令行参数,例如执行./nginx -g "daemon on"时,才会有这种状态。

//解析nginx配置文件,将解析后的配置值保存到各个模块的上下文结构中
char * ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
{
    enum 
	{
        parse_file = 0,				//开始解析配置文件
        parse_block,				//解析复杂块
        parse_param					//解析命令行参数,例如 nginx -g "daemon on"
    } type;
	//解析文件状态
    if (filename) 
	{
		//打开文件
        fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
		//系统调用获取文件状态,例如获取文件大小
        ngx_fd_info(fd, &cf->conf_file->file.info);
        cf->conf_file->buffer = &buf;
		//分配4K缓冲区,存放从配置文件读取的数据
        buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);
        buf.pos = buf.start;
        buf.last = buf.start;
        buf.end = buf.last + NGX_CONF_BUFFER;
        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;
        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 ( ;; )
	{
		//解析配置文件中的每一行记录,会把解析后的结果记录到ngx_conf_s中的args数组中保存。例如解析到worker_process 2;则args下标0存放worker_process,下标1存放2这个值
        rc = ngx_conf_read_token(cf);
		//解析复杂块完成,例如:解析完了event块,http块等。可以递归返回到上一层
        if (rc == NGX_CONF_BLOCK_DONE) 
		{
            goto done;
        }
		//整个文件解析完成
        if (rc == NGX_CONF_FILE_DONE)
		{
            goto done;
        }
		//开始解析复杂块,例如在处理http命令时会间接递归调用ngx_conf_parse函数本身
        if (rc == NGX_CONF_BLOCK_START)
		{
        }
		//这个回调函数一般用于处理text/html  text/css这种样式
		//回调函数为ngx_http_core_types。一般不走这个逻辑,而是走ngx_conf_handler逻辑
        if (cf->handler)
		{
            rv = (*cf->handler)(cf, NULL, cf->handler_conf);
                continue;
        }
		//根据解析的配置项,查找相应的命令
        rc = ngx_conf_handler(cf, rc);
    }
}
        ngx_conf_read_token主要做了两件事;1、判断是否需要从配置文件读取数据到缓冲区中,一般情况下在缓冲区数据都解析完成后,会从配置文件读取新数据到缓冲区。这部分内容可以参考"nginx配置解析之缓冲区管理"这篇文件的分析,这里就不在重复了。   2、解析配置文件中的每一行,从而得到配置项  配置值,并把结果保存到数组中。其中配置项与配置值之间使用空格隔开,在解析每一行时就会解析找到配置项与配置值之间的空格,从而获取到配置项。 每行以";"结尾,进而把找到的上一个空格与“;”之间的内容当做配置值。

        下面这个代码段就是以空格提取配置项,或者以";"提取配置值。进而将配置项保存到数组下标为0的位置,配置值保存到数组下标为1的位置。

static ngx_int_t ngx_conf_read_token(ngx_conf_t *cf)
{
	//查找到配置项与配置值的使用空格分割,或者一行结束时,说明找到配置项,或者配置值
	else if (ch == ' ' || ch == '\t' || ch == CR || ch == LF
                       || ch == ';' || ch == '{')
	{
		last_space = 1;
		found = 1;
	}
	//查到到一行的配置信息
	if (found)
	{	
		//获取一个空间,存放配置信息
		word = ngx_array_push(cf->args);
		word->data = ngx_pnalloc(cf->pool, b->pos - start + 1);
		//拷贝配置信息
		for (dst = word->data, src = start, len = 0;
			 src < b->pos - 1;
			 len++)
		{					
			*dst++ = *src++;
		}
	}			 
}

        而下面的代码段表示在已经找到配置项的条件下,跳过配置项之后的所有空格,开始查找配置值。

static ngx_int_t ngx_conf_read_token(ngx_conf_t *cf)
{
	//在已经找到空格情况下,跳过所有空格,从空格之后的内容查找
	//例如:worker_process        2; 查找到worker_process后第一个空格后,跳过所有的空格
	//last_space初始值为1
	if (last_space) 	
	{
		if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) 
		{
			continue;
		}
		//使用局部变量start,记录每一个有效内容开始。最终start与pos之间的内容构成配置项,或者配置值
		start = b->pos - 1;
		start_line = cf->conf_file->line;
		switch (ch)
		{

			case ';':
			case '{':							//开始处理复杂块
				return NGX_OK;
			case '}':							//复杂块解析结束
				return NGX_CONF_BLOCK_DONE;

			case '#':							//标记为注释
				sharp_comment = 1;
				continue;
		}
	}
}
        太细节的东西真不好分析,也没有必要。细节的东西还是读者自己慢慢分析吧! 到此nginx.conf配置文件的解析已经全部分析完成了。下一篇文章将分析http块的合并流程。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值