一句话脚本原理php,深入理解PHP原理之PHP脚本执行原理(2)

在上一篇《深入理解PHP原理之PHP脚本执行原理》文章中已经介绍了PHP的语法分析、词法分析和Opcodes的原理,下面主要通过一个具体的示例,来看看ZE是怎样对PHP脚本进行语法分析和词法分析,并将其编译为Opcodes,下面让我们回顾一下PHP脚本执行的基本流程:

1. Zend Engine(ZE)调用词法分析器(Lex生成的,源码路径:php/Zend/zend_language_sanner.l), 将我们要执行的PHP源文件去掉空格和注释后,分割成一个个的token;

2. ZE会将得到的token forward给语法分析器(yacc生成, 源码路径:php/Zend/zend_language_parser.y),将Tokens转换成简单而有意义的表达式;

3. ZE会将转换后的表达式,编译为一个个opcode,opcode一般会以op_array的形式存在,它是PHP执行的中间语言。

4. ZE调用zend_executor来执行op_array,输出结果。

下面通过具体示例来讲解上述过程,还是以经典的“hello world”为示例,如下:

$code =<<

$str = "Hello World";

$a = 1 + 1;

echo $a;

PHP_CODE;

var_dump(token_get_all($code));

函数token_get_all()可以将一段PHP代码 Scanning成tokens,分割后的结果如下:

array(19) {

[0]=>

array(3) {

[0]=>

int(367)

[1]=>

string(6) "<?php "

[2]=>

int(1)

}

[1]=>

array(3) {

[0]=>

int(370)

[1]=>

string(1) " "

[2]=>

int(2)

}

[2]=>

string(1) "="

[3]=>

array(3) {

[0]=>

int(370)

[1]=>

string(1) " "

[2]=>

int(2)

}

[4]=>

array(3) {

[0]=>

int(315)

[1]=>

string(13) ""Hello World""

[2]=>

int(2)

}

[5]=>

string(1) ";"

[6]=>

array(3) {

[0]=>

int(370)

[1]=>

string(2) " "

[2]=>

int(2)

}

[7]=>

string(1) "="

[8]=>

array(3) {

[0]=>

int(370)

[1]=>

string(1) " "

[2]=>

int(3)

}

[9]=>

array(3) {

[0]=>

int(305)

[1]=>

string(1) "1"

[2]=>

int(3)

}

[10]=>

array(3) {

[0]=>

int(370)

[1]=>

string(1) " "

[2]=>

int(3)

}

[11]=>

string(1) "+"

[12]=>

array(3) {

[0]=>

int(370)

[1]=>

string(1) " "

[2]=>

int(3)

}

[13]=>

array(3) {

[0]=>

int(305)

[1]=>

string(1) "1"

[2]=>

int(3)

}

[14]=>

string(1) ";"

[15]=>

array(3) {

[0]=>

int(370)

[1]=>

string(1) " "

[2]=>

int(3)

}

[16]=>

array(3) {

[0]=>

int(316)

[1]=>

string(4) "echo"

[2]=>

int(4)

}

[17]=>

array(3) {

[0]=>

int(370)

[1]=>

string(1) " "

[2]=>

int(4)

}

[18]=>

string(1) ";"

}

通过上面的返回结果,我们发现第一步“词法分析”将php脚本分割成数组array,该数组中包括php脚本中的空格、字符、操作数、语句等。在第二步“语法分析”中,数组array中的操作符、语句和标签等会根据文件php/Zend/zend_language_parser.y转换成tokens(下面是该文件部分源码,包含echo、while等关键字的tokens转换),但是字符串,字符,空格等不会,转换后的内容包含两个部分的Array:Token ID (也就是在Zend内部的改Token的对应码)和源码中的原来的内容。

%token T_IF "if (T_IF)"

%token T_ELSEIF "elseif (T_ELSEIF)"

%token T_ELSE "else (T_ELSE)"

%token T_ENDIF "endif (T_ENDIF)"

%token T_ECHO "echo (T_ECHO)"

%token T_DO "do (T_DO)"

%token T_WHILE "while (T_WHILE)"

然后就是Parsing阶段,Parsing首先会丢弃Tokens Array中的多余的空格,然后将剩余的Tokens转换成一个个简单的表达式,Parsing后的结果大致如下:

* ZEND_ECHO 'Hello World' // echo a constant string

* ZEND_ADD ~0 1 1 // add two numbers together

* ZEND_ASSIGN !0 ~0 // store the result of the prior expression to a variable

* ZEND_ECHO !0 // echo a variable

第三步就是Compilation阶段,它会把Parsing后的每个表达式编译成一个个zend_op,让我们回顾一下zend_op的结构:

struct _zend_op {

opcode_handler_t handler; // 执行该opcode时调用的处理函数

znode result; // 执行完后的返回结果

znode op1; // 操作数1

znode op2; // 操作数2

ulong extended_value; // 执行时可能还需要用到的其它信息

uint lineno;

zend_uchar opcode; // opcode代码

};

第四步就是将Compilation阶段生成的zend_op整合成op_array,然后就可以通过zend_executor执行,整个过程就结束了,你可能会问了,我们的$a去那里了?下面就直接引用鸟哥的解释,因为最后一个地方我也没看懂 /(ㄒoㄒ)/~~

这个要介绍操作数了,每个操作数都是由以下俩个部分组成:

a)op_type : 为IS_CONST, IS_TMP_VAR, IS_VAR, IS_UNUSED, or IS_CV

b)u,一个联合体,根据op_type的不同,分别用不同的类型保存了这个操作数的值(const)或者左值(var)

对于var来说,每个var也不一样:

IS_TMP_VAR,顾名思义,这个是一个临时变量,保存一些op_array的结果,以便接下来的op_array使用,这种操作数的u保存着一个指向变量表的一个句柄(整数),这种操作数一般用~开头,比如~0表示变量表的0号未知的临时变量;

IS_VAR 这种就是我们一般意义上的变量了,他们以$开头表示;

IS_CV 表示ZE2.1/PHP5.1以后的编译器使用的一种cache机制,这种变量保存着被它引用的变量的地址,当一个变量第一次被引用的时候,就会被CV起来,以后对这个变量的引用就不需要再次去查找active符号表了,CV变量以!开头表示。

这么看来,我们的a被优化成!0了。(这里没看懂,为什么变成!0了???)

参考:http://www.laruence.com/2008/06/18/221.html

参考:http://www.php-internals.com/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值