nginx服务器的master进程在解析nginx.conf时,会使用一个4k大小的缓存区存放部分配置文件信息。nginx会从配置文件中读取4k大小的内容到缓冲区,之后对缓冲区中的内容进行逐个字符扫描,从而解析出配置项、配置值。例如server_name 127.0.0.1; 则逐个字符扫描得到配置项server_name, 配置值127.0.0.1。当然nginx不会每次都去读取文件,而是读取4k大小的内容到缓冲区中,然后对缓冲区的内容进行解析。如果缓冲区的内容都解析完了,才会从配置文件中重新读取4k大小的内容到缓冲区中。
因此这个缓冲区可以被重复利用。那为什么不一次性读取配置文件中的所有内容到缓冲区,然后对缓冲区的内容进行解析呢? 因为在极端情况下,如果nginx.conf内容非常大,例如超过1G,则把这1G内容一次性读取到内容缓冲区中,太占用内存资源。因此为了解决配置文件过大问题,使用一个4K的缓冲区进行管理,限制每次读取到缓冲区数据大小。 以下是缓冲区可能存在的几种状态。
一、开始解析配置时,ngx_conf_parse函数会从配置中读取4K大小的内容内容到缓冲(前提是nginx.conf配置文件大小超过4k)。函数调用后内存布局如下,缓冲区的数据全部为待解析数据,pos,start指向缓冲区的开始位置;last,end指向缓冲区的结束位置。接下里nginx会对这个缓冲区中的数据进行解析,从而获取到配置项、配置值。
二、当nginx解析完一部分数据后,一部分数据已经解析成“配置项 配置值;"了,另一部分数据则已经扫描到了,但不足以组装成一行数据(也就是配置项 配置值;); 还有一部分则表示完全没有扫描到。
已解析数据: 这部分的数据表示已经能够组装成一各个独立行数据,也就是("配置项 配置值;")格式。例如:
work_process 2;
error_log logs/error.log;
这部分已解析数据已经保存到了ngx_conf_s结构中的args数组中了,因此这部分空间可以被重新利用。
已扫描数据: 这部分数据表示不能够组装成一个个独立行数据,也就是("配置项 配置值;")格式。例如:本来一个完整的行格式为:worker_connections 1024;
但实际上只解析到了worker_conectio这一小部分数据,因此不能构成一个完整的格式。这部分数据称为已扫描数据,还不能被删除。nginx使用了局部变量start,start与pos之间的这部分数据就是已扫描数据。
未扫描数据: 这部分数据表示nginx还没有解析到这部分数据,pos与last之间的这部分数据称为未扫描数据。
三、当缓冲区里的字符都处理完成时,需要从配置文件中读取新的内容到缓冲区中,此时的临界状态为:
四、已解析的数据已经保存到数组中了,在缓存中没有意义了。这部分数据可以被删除。而start与pos之间这部分数据,由于还不能为组装成一个完成的格式,因此还不能被删除。但由于缓冲期已经解析完了,因此需要从配置文件中读取新的数据到缓冲区中。此时需要把这部分已经扫描的数据移动到缓冲区最开始的位置。然后从配置文件中读取新数据到这个已扫描缓冲区之后。
如果配置文件剩余数据没有4K大小,也就是读取后的内容不能填满整个缓冲区。那这种场景下缓冲区布局如下,此时空白空域表示缓冲区未满。
以上是在解析nginx.conf配置文件时,可能存在的5种缓冲区状态。在理清了配置文件在缓冲区中的内存布局后,接下来分析nginx如何解析配置文件就相对容易些了。ngx_conf_parse函数是解析配置文件的入口,内部会调用ngx_conf_read_token函数,这个函数负责从配置文件中读取数据到缓冲区,以及对缓冲区中的数据进行扫描得到完整的"配置项 配置值;"格式的内容;以下代码片段就是上面5种可能的缓冲区内存布局的代码实现。
ngx_int_t ngx_conf_read_token(ngx_conf_t *cf)
{
for(;;)
{
//是否需要从配置文件读取配置到缓冲区条件。也就是缓冲区的所有内容都已经被扫描到了。
//一部分数据已经解析为配置项配置值格式。 另一部分扫描到了,但还没解析为配置项配置值格式。
//说明缓冲区内容已经不能够组装完整的配置项配置值内容格式,需要从文件中读取数据到缓冲区
if (b->pos >= b->last)
{
if (cf->conf_file->file.offset >= file_size)
{
//如果文件已经读取完成,但数组中参数个数不为0,说明配置文件格式错误。
//因为每解析完一行配置,会把数组中的参数保存到内容结构中。之后会把这个参数数组元素清空。
//以便存放下一行解析出的参数信息。文件都结束了,但数组元素还存在,说明配置有误
if (cf->args->nelts > 0)
{
if (cf->conf_file->file.fd == NGX_INVALID_FILE)
{
return NGX_ERROR;
}
return NGX_ERROR;
}
return NGX_CONF_FILE_DONE;
}
//len长度的空间表示已经扫描完成,但没有解析成一个个配置项配置值格式
//start为局部变量(pos与start之间的数据为已扫描数据)
len = b->pos - start;
//已经扫描但未组装成一个配置项配置值格式的内容。说明这一行长度太大了。配置有问题
if (len == NGX_CONF_BUFFER)
{
cf->conf_file->line = start_line;
return NGX_ERROR;
}
//len表示上一次读取后,还有多少内容未解析。并将这些未解析的内容移动到缓冲区头部
//下一次读取的内容就可以放到缓冲区的len长度后面
if (len)
{
ngx_memmove(b->start, start, len);
}
//计算配置文件剩余大小
size = (ssize_t) (file_size - cf->conf_file->file.offset);
//每一次最多读取多少字节内容,如果文件很大,大于缓冲区4096大小,则每次最多
//从文件中读取4096字节的内容
if (size > b->end - (b->start + len))
{
size = b->end - (b->start + len);
}
//从文件中读取数据到缓冲区
n = ngx_read_file(&cf->conf_file->file, b->start + len, size,
cf->conf_file->file.offset);
b->pos = b->start + len;
b->last = b->pos + n;
start = b->start;
}
}
}