深入理解PHP原理之PHP脚本执行原理(2)

本文深入探讨PHP脚本执行过程,从词法分析、语法分析到编译为Opcodes,详细解析PHP如何将代码转化为可执行的中间语言。通过实例解释词法分析如何将PHP源码分割成tokens,语法分析如何转换为有意义的表达式,以及编译阶段生成的zend_op如何整合成op_array,最后通过executor执行。
摘要由CSDN通过智能技术生成

往期精选(欢迎转发~~)

在上一篇《深入理解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 =<<<PHP_CODE
<?php
$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、付费专栏及课程。

余额充值