「深入理解PHP内核」 - PHP脚本的执行

PHP脚本的执行

编程语言的作用就是将语言的特定符号和处理规则进行翻译,由编程语言来处理这些规则,目前有非常多的编程语言,不管是静态语言还是动态语言都有固定的工作要做:将代码编译为目标指令,而编译过程就是根据语言的语法规则来进行翻译,我们可以选择手动对代码进行解析,但这是一个非常枯燥而容易出错的工作,因此就出现了像lex/yacc这类的编译器生成器

编程语言的编译器或解释器一般包含两大部分:

  • 读取源程序,并处理语言结构
  • 处理语言结构并生成目标程序

Lex和Yacc可以解决第一个问题,第一部分也可以分成两个部分:

  • 将代码分为一个个的标记
  • 处理程序的层级结构

很多编程语言都使用lex/yacc或者他们的变体来作为语言的词法语法分析生成器,比如PHP,Ruby,Python以及MySQL的SQL语言实现

Lex和Yacc是Unix下的两个文本处理工具,主要用于编写编译器,也可以做其他用途

  • Lex 词法分析生成器 A Lexiacal Analyzer Generator
  • Yacc Yet Another Complier-Complier

Lex/Fex

Lex读取词法规则文件,生成词法分析器。目前通常是会用Flex以及Bison来完成同样的工作。

词法规则文件一般以.l作为扩展名,flex文件由三个部分组成,三部分之间用%%分割

1
2
3
4
5
定义段
%%
规则段
%%
用户代码段

例如一下一个用于统计文件字符、词以及行数的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
%option noyywrap
%{
int chars = 0
int words = 0
int lines = 0;
%}

%%
[a-zA-Z]+ {words++; chars+= strlen(yytext);}
{chars++; lines++;}
{chars ++;}
%%

main(int argc, char **argv)
{
if (argc > 1) {
if(!(yyin=fopen(argv[1], "r"))) {
perror(argv[1]);
return (1);
}
yylex();
printf("%8d%8d%8d\n", lines, words, chars);
}
}

该解释器读取文件内容, 根据规则段定义的规则进行处理, 规则后面大括号中包含的是动作, 也就是匹配到该规则程序执行的动作, 这个例子中的匹配动作时记录下文件的字符,词以及行数信息并打印出来。其中的规则使用正则表达式描述。

PHP以前使用的是flex,后来PHP的词法解析改为使用re2c, $PHP_SRC/Zend/zend_language_scanner.l 文件是re2c的规则文件, 所以如果修改该规则文件需要安装re2c才能重新编译

Yacc/Bison

PHP在后续的版本中可能会使用Lemon作为语法分析器, Lemon是SQLite作者为SQLite中SQL所编写的词法分析器。 Lemon具有线程安全以及可重入等特点,也能提供更直观的错误提示信息。

下面是以PHP中echo语句的编译为例:echo可以接受多个参数,这几个参数之间可以使用逗号分隔,在PHP的羽凡规则如下:

1
2
3
4
echo_expr_list:
echo_expr_list ',' expr { zend_do_echo(&$3 TSRMLS_CC); }
| expr { zend_do_echo(&$1 TSRMLS_CC); }
;

其中echo_expr_list规则为一个递归规则,这样就允许接受多个表达式作为参数。 在上例中当匹配到echo时会执行zend_do_echo函数, 函数中的参数可能看起来比较奇怪, 其中的$3 表示前面规则的第三个定义,也就是expr这个表达式的值, zend_do_echo函数则根据表达式的信息编译opcode,其他的语法规则也类似。 这和C语言或者Java的编译器类似,不过GCC等编译器时将代码编译为机器码,Java编译器将代码编译为字节码。

##opcode

opcode是计算机指令中的一个部分,用于指定要执行的操作,指令的格式和规范由处理器的指令规范执行。除了指令本身以外通常还有指令所需要的操作数,可能有的指令不需要显示的操作数。这些操作数可能是寄存器中的值,堆栈中的值,某块内存的值或者IO端口中的值等等。

通常opcode还有另外一种称为:字节码(byte codes)

#####PHP的opcode

PHP是构建在Zend虚拟机Zend VM之上的,PHP 的opcode就是Zend虚拟机中的指令。

在PHP实现内部,opcode由如下的结构体表示:

1
2
3
4
5
6
7
8
9
struct _zend_op {
opcode_handler_t handler; // 执行该opcode时调用的处理函数
znode result;
znode op1;
znode op2;
ulong extended_value;
uint lineno;
zend_uchar opcode; // opcode代码
}

和CPU指令类似,有一个标示指令的opcode字段,以及这个opcode所操作的操作数,PHP不像汇编那么底层,在脚本实际执行的时候可能还需要其他更多的信息,extended_value字段就保存了这类信息,其中的result域则是保存该指令执行完成后的结果