NGINX----源码阅读---配置文件解析(由简到杂)
我们知道nginx的配置是从配置文件中读取的,那么nginx是怎么将这些配置文件对应的配置项和值填入对应模块的内存的。下面我们将从简单的配置解析逐渐到整个配置的解析进行。
打开nginx.conf可以看到配置文件中有简单配置和复杂配置,我们这里先解析只有简单的配置,因此对配置解析的代码进行精简,只提取有用的部分。
1.配置文件nginx.conf,且配置文件中只有
worker_processes 1;
nginx的main函数在nginx.c文件中,关于配置的开始可以理解在ngx_init_cycle函数中调用所有模块的create_conf钩子这里,不过这节我们不关注这个。后边的代码就是如何将配置文件的配置解析到对应的模块里边的。
入口函数为ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
ngx_conf_parse函数的入参cf理解为配置的内存存储,存放了配置名称、参数等,filename就是将要解析的配置文件名。
首先解析nginx.conf配置,需要进行文件读入操作。
以下代码主要的工作是:
1)打开配置文件nginx.conf;
2)初始化conf cf,存储配置文件的基本信息(文件名、文件读取位置、读取文件的缓存等)关于配置文件相关的结构体可以查看:。。。。。
1 if (filename) { 2 3 /* open configuration file */ 4 5 fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); 6 if (fd == NGX_INVALID_FILE) { 7 ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, 8 ngx_open_file_n " \"%s\" failed", 9 filename->data); 10 return NGX_CONF_ERROR; 11 } 12 13 prev = cf->conf_file; 14 15 cf->conf_file = &conf_file; 16 //ngx_fd_info 将fd文件的状态拷贝到sb中 17 //define ngx_fd_info(fd, sb) fstat(fd, sb) 18 //typedef struct stat ngx_file_info_t; 19 if (ngx_fd_info(fd, &cf->conf_file->file.info) == NGX_FILE_ERROR) { 20 ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, 21 ngx_fd_info_n " \"%s\" failed", filename->data); 22 } 23 24 cf->conf_file->buffer = &buf; 25 26 buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log); 27 if (buf.start == NULL) { 28 goto failed; 29 } 30 31 buf.pos = buf.start; 32 buf.last = buf.start; 33 buf.end = buf.last + NGX_CONF_BUFFER; 34 buf.temporary = 1; 35 36 cf->conf_file->file.fd = fd; 37 cf->conf_file->file.name.len = filename->len; 38 cf->conf_file->file.name.data = filename->data; 39 cf->conf_file->file.offset = 0; 40 cf->conf_file->file.log = cf->log; 41 cf->conf_file->line = 1; 42 43 type = parse_file; 44 }
文件打开后,并且缓存准备就绪,就可以开始读文件了。
1 for ( ;; ) { 2 rc = ngx_conf_read_token(cf); 3 }
进入循环进行配置文件的读取。我们进入ngx_conf_read_token看下。
函数的开始同样是一些基础数据的声明,这里先放这里,可以回头看下。
1 u_char *start, ch, *src, *dst; //start 缓存的开始 ch当前读取的字符 2 off_t file_size; //配置文件的大小 3 size_t len; //一次读取的字符数 4 ssize_t n, size; // 5 ngx_uint_t found, need_space, last_space, sharp_comment, variable;//found是否找到token needspace是否使用空格 last_space开始 sharp_comment评论标示 6 ngx_uint_t quoted, s_quoted, d_quoted, start_line; //quoted是/的标示 s_quoted是'的标示 d_quoted"的标示 start_line 7 ngx_str_t *word; 8 ngx_buf_t *b, *dump; 9 10 /*初始化上边的基础变量*/ 11 found = 0; 12 need_space = 0; 13 last_space = 1; 14 sharp_comment = 0; 15 variable = 0; 16 quoted = 0; 17 s_quoted = 0; 18 d_quoted = 0; 19 20 //配置文件的参数个数 21 cf->args->nelts = 0; 22 //配置文件的缓存,用于读取配置文件暂存字符的地方 23 b = cf->conf_file->buffer; 24 dump = cf->conf_file->dump; 25 //缓存起始地址 26 start = b->pos; 27 //配置文件的起始行 28 start_line = cf->conf_file->line; 29 30 //配置文件的大小 31 file_size = ngx_file_size(&cf->conf_file->file.info);
配置文件的字符解析
for ( ;; ) { //如果当前缓存已经读完,则需要从文件中继续读入数据 if (b->pos >= b->last) { //表示当前文件已经读取完毕,但是需要进行最后的校验,如果参数还有未解析的或者开始解析为1报错 if (cf->conf_file->file.offset >= file_size) { if (cf->args->nelts > 0 || !last_space) { //配置项后缺少分号 if (cf->conf_file->file.fd == NGX_INVALID_FILE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected end of parameter, " "expecting \";\""); return NGX_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected end of file, " "expecting \";\" or \"}\""); return NGX_ERROR; } //配置文件解析完成 return NGX_CONF_FILE_DONE; } //表示当前已经解析的缓存字符长度 len = b->pos - start; //表示缓存中的字符解析完成??????没搞明白 if (len == NGX_CONF_BUFFER) { cf->conf_file->line = start_line; if (d_quoted) { ch = '"'; } else if (s_quoted) { ch = '\''; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "too long parameter \"%*s...\" started", 10, start); return NGX_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "too long parameter, probably " "missing terminating \"%c\" character", ch); return NGX_ERROR; } //将start后的len字符移到缓存最前边 if (len) { ngx_memmove(b->start, start, len); } //size表示文件还没有被读的部分大小 size = (ssize_t) (file_size - cf->conf_file->file.offset); //取剩余文件字符数和剩余缓存数的最小值,作为读取文件的字符数 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); if (n == NGX_ERROR) { return NGX_ERROR; } if (n != size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ngx_read_file_n " returned " "only %z bytes instead of %z", n, size); return NGX_ERROR; } //pos指向缓存已经解析的位置 b->pos = b->start + len; //last指向缓存的结尾 b->last = b->pos + n; //start指向缓存的开始 start = b->start; if (dump) { dump->last = ngx_cpymem(dump->last, b->pos, size); } }
对于worker_processes 1;配置,我们的目的是将work_processes 和1两个字段添加到入参数组中arr,
首先考虑的是对worker_processes前的空格要忽略掉;
然后是结尾,遇到;就表示一次配置的读取完成;
那么代码实现如下:
1 for(;;){ 2 ch = *b->pos++; 3 if (last_space) { 4 if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) { 5 continue; 6 } 7 switch (ch) { 8 default: 9 last_space = 0; 10 } 11 } 12 else 13 { 14 if (ch == ' ' || ch == '\t' || ch == CR || ch == LF 15 || ch == ';') 16 { 17 last_space = 1; 18 found = 1; 19 } 20 if (found) { 21 word = ngx_array_push(cf->args); 22 if (word == NULL) { 23 return NGX_ERROR; 24 } 25 word->data = ngx_pnalloc(cf->pool, b->pos - 1 - start + 1); 26 if (word->data == NULL) { 27 return NGX_ERROR; 28 } 29 if (ch == ';') { 30 return NGX_OK; 31 } 32 found = 0; 33 } 34 }
以上代码只考虑了这种简单情况;
这时我们加入双引号或者单引号,就需要在解析到对应的token时标示上当前token是带有引号的,并且存放入参数组是需要去掉引号。由于单引号和双引号等价,我们只考虑单引号情况。
多了单引号的介入,我们想到的方法是增加tag来标示解释过程中的引号匹配,读到左单引号就要加tag(s_quoted)token结束时应该有右单引号。
加入引号后,解析到左单引号时,标记s_quoted标示,标示字段必须以右单引号结束。
对于有单引号的字段后边的解析要加一个逻辑,配对的右单引号右边必须紧跟一个空格,或者分号结束,因此加上了needspace约束标志,标示解析完带有单引号的字段后必须要跟一个空格再继续解析,否则格式错误。
for(;;){ ch = *b->pos++; if (need_space) { if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) { last_space = 1; need_space = 0; continue; } if (ch == ';') { return NGX_OK; } if (ch == ')') { last_space = 1; need_space = 0; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"%c\"", ch); return NGX_ERROR; } } if (last_space) { if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) { continue; } switch (ch) { case '\'': start++; s_quoted = 1; last_space = 0; continue; default: last_space = 0; } } else { if (s_quoted) { if (ch == '\'') { s_quoted = 0; need_space = 1; found = 1; } } else if (ch == ' ' || ch == '\t' || ch == CR || ch == LF || ch == ';') { last_space = 1; found = 1; } if (found) { word = ngx_array_push(cf->args); if (word == NULL) { return NGX_ERROR; } word->data = ngx_pnalloc(cf->pool, b->pos - 1 - start + 1); if (word->data == NULL) { return NGX_ERROR; } if (ch == ';') { return NGX_OK; } found = 0; } } }