Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1100
  • 博文数量: 1
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2015-02-07 17:06
文章分类
文章存档

2015年(1)

我的朋友
最近访客

分类: Mysql/postgreSQL

2015-02-07 21:26:56

    最近在啃PostgreSQL的源码,看到了postgres进程加载配置这一块。对于服务器编程,加载配置几乎是不可避免的,因为为了可定制可配置,工程中总有一些配置项,在进程启动的时候加载,当然也可以进程运行过程中改变配置项。不同的项目,管理这些配置项的方法也各不相同。片汤话不多说,先看下PostgreSQL启动过程
 
    pg_ctl提供了很多选项,比如stop,restart ,reload,status,我们上图给出的是start选项调用的流程。可以看到pg_ctl最后通过system函数,调用了另一可执行程序 postgres。稍等,PostgreSQL是如何找到可执行文件postgres的路径的?do_start函数中
  1. exec_path = find_other_exec_or_die(argv0, "postgres", PG_BACKEND_VERSIONSTR)
是用来查找postgres的路径的。
    postgres可执行文件的main函数的代码路径是在src/backend/main下面。postgres支持很多的参数,比如我们可以通过
  1. postgres --describe-config
来获取postgre的当前配置。可以选择--single来进入single模式。这都不是我们今天的重点,所以我就不罗嗦了。
    我们看下postgres代码流程如何进入解析postgres.conf的

这一部分逻辑在PostMasterMain比较早的地方进行的,初始化完内存内存上下文,初始化玩GUCoption,解析完入参,就开始解析配置文件了。
    ParseConfigFile这个函数位于src/backend/utils/misc/guc-file.c中,这个guc-file.c很有意思,代码看的莫名其妙。刚开始看GUC_yylex看的我一头雾水,不知所云,代码跟踪到yylex跟踪不下去了。后来发现目录下还有一个guc-file.l的文件。我就差不多想到了这肯定与传说中的yacc and lex有关系。 我首先翻看了Makefile,找到了如下code:
  1. guc-file.c: guc-file.l
  2. ifdef FLEX
  3.     $(FLEX) $(FLEXFLAGS) -o'$@' $<
  4. else
  5.     @$(missing) flex $< $@
  6. endif
    果然,guc-file.c是flex从guc-file.l中生成的。这就需要了解传说中的yacc and lex 以及传说中的flex and bison了。
    flex,全称是Fast Lexical Analyzer Generator。前身是lex。历史就比较有趣了,Eric Schmidt在1975年写了一个工具lex,这个工具被看作是yacc的兄弟,很有名气,尽管有点慢,而且bug有点多。1987年Vern Paxson写了flex,这个flex就比较好了,比较快,又可靠。值得一提的是Eric Schmidt,这个名字很熟悉吧,这是Google的执行主席。一起膜拜下大牛:
           
    guc-file.l里面的解析稍微有点复杂,不利于我们入门。我找了Flex and bison这本书,练习了第一章的例子,算是基本入了门。
    这个例子就如同Linux下的wc工具。我们看下代码:
  1. root@manu:~/code/flex_bison# cat wc.l
  2. /* just like Unix wc */
  3. %{
  4.     int chars = 0;
  5.     int words = 0;
  6.     int lines = 0;
  7. %}
  8.     
  9. %%
  10. [a-zA-Z]+ { words++; chars += strlen(yytext); }
  11. \n        { chars++; lines++; }
  12. .         { chars++; }
  13. %%

  14. int main(int argc ,char* argv[])
  15. {
  16.     yylex();
  17.     printf("%8d%8d%8d\n", lines, words, chars);
  18.     return 0;
  19. }
    flex程序分成三个部分,他们之间以%%隔开。第一部分是声明和配置选项设定(option setting),第二部分是模式和一些模式匹配后的动作,第三部分是C code。
    要想将wc.l编译成C文件,需要安装flex,这我就不说了,apt-get install就好了。
  1. root@manu:~/code/flex_bison# flex -o wc.c wc.l
  2. root@manu:~/code/flex_bison# ll
  3. 总用量 60
  4. drwxr-xr-x 3 root root  4096 4月 5 13:22 ./
  5. drwxr-xr-x 9 manu root  4096 4月 4 22:10 ../
  6. -rw-r--r-- 1 root root 44406 4月 5 13:22 wc.c
  7. -rw-r--r-- 1 root root   313 4月 5 13:21 wc.l
 我们看到生成了wc.c,然后调用gcc可以编译出可执行文件,注意要添加-lfl选项:

  1. root@manu:~/code/flex_bison# ./wc
  2. hello world!
  3. this is a new line
  4.        2 7 32
  5. root@manu:~/code/flex_bison#
可以看到wc程序帮我们统计了行数,单词数以及字符数。
    我们看下PostgreSQL中guc-file.l中干的事情:
  1. SIGN            ("-"|"+")
  2. DIGIT           [0-9]
  3. HEXDIGIT        [0-9a-fA-F]

  4. UNIT_LETTER     [a-zA-Z]

  5. INTEGER         {SIGN}?({DIGIT}+|0x{HEXDIGIT}+){UNIT_LETTER}*

  6. EXPONENT        [Ee]{SIGN}?{DIGIT}+
  7. REAL            {SIGN}?{DIGIT}*"."{DIGIT}*{EXPONENT}?

  8. LETTER          [A-Za-z_\200-\377]
  9. LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]

  10. ID              {LETTER}{LETTER_OR_DIGIT}*
  11. QUALIFIED_ID    {ID}"."{ID}

  12. UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])*
  13. STRING          \'([^'\\\n]|\\.|\'\')*\
    这一部分本质是定义分词的规则,或者说定义pattern。+和-定义为符号,是正还是负,[0-9a-fA-F]定义为16进制数字,以此类推。从这个例子我们也能看出来为什么叫词法分析,本质就是切词。把很长的文本,用一定的规则,进行切成一个一个的token,或者一个个的小块。
    怎么切分,就是模式匹配。定义好一些模式,匹配上的部分,被切出来,做相应的动作,如我们的wc程序,直接统计计数++,或者贴上相应的标签,如PostgreSQL中guc-file.l中那样,给第三部分进行处理。

  1. \n                  ConfigFileLineno++; return GUC_EOL;
  2. [ \t\r]+            /* eat whitespace */
  3. #.*                 /* eat comment (.* matches anything until newline) */

  4. {ID}                return GUC_ID;
  5. {QUALIFIED_ID}      return GUC_QUALIFIED_ID;
  6. {STRING}            return GUC_STRING;
  7. {UNQUOTED_STRING}   return GUC_UNQUOTED_STRING;
  8. {INTEGER}           return GUC_INTEGER;
  9. {REAL}              return GUC_REAL;
  10. =                   return GUC_EQUALS;

  11. .                   return GUC_ERROR;

  12. %%
    切出来一个个的token怎么处理呢,先看下postgres.conf是格式:
  1. datestyle = 'iso, ymd'
  2. #intervalstyle = 'postgres'
  3. timezone = 'PRC'

  4. autovacuum_vacuum_threshold = 50    
  5. cpu_index_tuple_cost = 0.005
    postgres配置文件里面的格式不外乎就这么几类,首先#开头的行都不需要处理了,因为comment都被忽略了。
  1. #.* /* eat comment (.* matches anything until newline) */
除去comment行,一般配置项都是这种option_name = option_value的格式。前面是配置项的名字,后面是配置项的值。
    配置项的值类型有不同,有些值是string类型,有些是INT型的整数,有些是REAL型的实数。理解到此处,第三部分处理分词部分,整体就比较简单了。基本奉行一下几点:
1 #开头的 comment行不处理
2 每行开头的token必须是GUC_ID or GUC_QUALIFIED_ID类型,可以想下,如果配置项的某一行是 0.4=abc,明显是不合法的,因为我们期待的格式是option_name=option_value

  1. if (token != GUC_ID && token != GUC_QUALIFIED_ID)
  2.             goto parse_error;
  3.         opt_name = pstrdup(yytext)
3 option_name 之后,期待一个等号=,没有等号也没关系。将等号后面的token赋给option_value,字符串类型的话,特殊处理下,因为有转义字符之类的情况。

  1.         token = yylex();
  2.         if (token == GUC_EQUALS)
  3.             token = yylex();

  4.         /* now we must have the option value */
  5.         if (token != GUC_ID &&
  6.             token != GUC_STRING &&
  7.             token != GUC_INTEGER &&
  8.             token != GUC_REAL &&
  9.             token != GUC_UNQUOTED_STRING)
  10.             goto parse_error;
  11.         if (token == GUC_STRING)    /* strip quotes and escapes */
  12.             opt_value = GUC_scanstr(yytext);
  13.         else
  14.             opt_value = pstrdup(yytext)
4 如果当前配置文件A包含了其他配置文件B,需要递归处理,解析配置文件B里面的配置
  1. if (guc_name_compare(opt_name, "include_if_exists") == 0)
  2.         {
  3.             /*
  4.              * An include_if_exists directive isn't a variable and should be
  5.              * processed immediately.
  6.              */
  7.             if (!ParseConfigFile(opt_value, config_file, false,
  8.                                  depth + 1, elevel,
  9.                                  head_p, tail_p))
  10.                 OK = false;
  11.             yy_switch_to_buffer(lex_buffer);
  12.             pfree(opt_name);
  13.             pfree(opt_value);
  14.         }
  15.         else if (guc_name_compare(opt_name, "include") == 0)
  16.         {
  17.             /*
  18.              * An include directive isn't a variable and should be processed
  19.              * immediately.
  20.              */
  21.             if (!ParseConfigFile(opt_value, config_file, true,
  22.                                  depth + 1, elevel,
  23.                                  head_p, tail_p))
  24.                 OK = false;
  25.             yy_switch_to_buffer(lex_buffer);
  26.             pfree(opt_name);
  27.             pfree(opt_value);
  28.         }
    OK,解释到此,基本没啥好讲的了。几乎稍微大的项目,都会遇到解析配置项这个问题。我们team也不例外,当然我们team采用另外一套方法保存配置项加载配置项,信息安全考虑,我就不介绍我们team的方法了。PostgreSQL采用了用flex进行分词,获取option_name和option_value。通过这个例子,我初步学习了flex,其实也学习了下bison。和本主题无关,就不多讲了。

参考文献:
1 PostgreSQL 9.2.3 source code
2 flex and bison


阅读(534) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:没有了

给主人留下些什么吧!~~