php的执行过程解释

语言是人们进行沟通和交流的表达符号,每种语言都有专属于自己的符号,表达方式和规则。 就编程语言来说,它也是由特定的符号,特定的表达方式和规则组成。语言的作用是沟通,不管是自然语言,还是编程语言,它们的区别在于自然语言是人与人之间沟通的工具, 而编程语言是人与机器之间的沟通渠道。

PHP语言来说,它也是一组符合一定规则的约定的指令。 在编程人员将自己的想法以PHP语言实现后,通过PHP的虚拟机(确切的来说应该是PHP的语言引擎Zend)

将这些PHP指令转变成C语言 (可以理解为更底层的一种指令集)指令,而C语言又会转变成汇编语言, 最后汇编语言将根据处理器的规则转变成机器码执行。这是一个更高层次抽象的不断具体化,不断细化的过程。

从一种语言到另一种语言的转化称之为编译,这两种语言分别可以称之为源语言和目标语言。 这种编译过程通过发生在目标语言比源语言更低级(或者说更底层)。 语言转化的编译过程是由编译器来完成, 编码器通常被分为一系列的过程:词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成等。 前面几个阶段(词法分析、语法分析和语义分析)的作用是分析源程序,我们可以称之为编译器的前端。 后面的几个阶段(中间代码生成、代码优化和目标代码生成)的作用是构造目标程序,我们可以称之为编译器的后端。 一种语言被称为编译类语言,一般是由于在程序执行之前有一个翻译的过程, 其中关键点是有一个形式上完全不同的等价程序生成。 而PHP之所以被称为解释类语言,就是因为并没有这样的一个程序生成, 它生成的是中间代码Opcode,这只是PHP的一种内部数据结构

二、 PHP代码的执行的过程

比如我们写一个简单的程序

<?php  
    echo "Hello World!";  
    $a = 1 + 1;  
    echo $a;  
?>  

这个简单的程序他执行过程是怎样的呢?其实,执行过程也正如我们前面所说分为4个步骤。(这里只是指PHP语言引擎Zend执行过程,不包含Web服务器的执行过程。)

  1. 1.Scanning(Lexing) ,将PHP代码转换为语言片段(Tokens)

  2. 2.Parsing, 将Tokens转换成简单而有意义的表达式

  3. 3.Compilation, 将表达式编译成Opocdes

  4. 4.Execution, 顺次执行Opcodes,每次一条,从而实现PHP脚本的功能。

    注1:Opcode是一种PHP脚本编译后的中间语言,就像Java的ByteCode,或者.NET的MSL

注2:现在有的Cache比如APC,可以使得PHP缓存住Opcodes,这样,每次有请求来临的时候,就不需要重复执行前面3步,从而能大幅的提高PHP的执行速度。

将PHP代码转换为语言片段(Tokens)

那什么是Lexing? 学过编译原理的同学都应该对编译原理中的词法分析步骤有所了解,Lex就是一个词法分析的依据表。

对于PHP在开始使用的是Flex,之后改为re2c, MySQL的词法分析使用的Flex,除此之外还有作为UNIX系统标准词法分析器的Lex等。 这些工具都会读进一个代表词法分析器规则的输入字符串流,然后输出以C语言实做的词法分析器源代码。 这里我们只介绍PHP的现版词法分析器,re2c。

在源码目录下的Zend/zend_language_scanner.l 文件是re2c的规则文件, 如果需要修改该规则文件需要安装re2c才能重新编译,生成新的规则文件。

Zend/zend_language_scanner.c会根据Zend/zend_language_scanner.l,来输入的 PHP代码进行词法分析,从而得到一个一个的“词”。

从PHP4.2开始提供了一个函数叫token_get_all,这个函数就可以将一段PHP代码 Scanning成Tokens;

我们用下面的代码使用token_get_all函数处理我们开头提到的PHP代码。

<?php  
echo "<pre>";  
$phpcode = <<<PHPCODE  
<?php  
    echo "Hello World!";  
    $a = 1 + 1;  
    echo $a;  
?>  
PHPCODE;  
// $tokens = token_get_all($phpcontent);  
// print_r($tokens);  
$tokens = token_get_all($phpcode);   
foreach ($tokens as $key => $token) {  
    $tokens[$key][0] = token_name($token[0]);  
}  
print_r($tokens);  
?>  

为了便于理解和查看,我使用token_name函数将解析器代号修改成了符号名称说明。

如果有的童鞋想要看原始的,可以将上面代码中的第10,11行代码注释去掉。

解释器代号列表详见:http://www.php.NET/manual/zh/tokens.php

得到的结果如下:

Array  
(  
    [0] => Array  
        (  
            [0] => T_OPEN_TAG  
            [1] =>  1  
        )  
  
    [1] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>     
            [2] => 2  
        )  
  
    [2] => Array  
        (  
            [0] => T_ECHO  
            [1] => echo  
            [2] => 2  
        )  
  
    [3] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 2  
        )  
  
    [4] => Array  
        (  
            [0] => T_CONSTANT_ENCAPSED_STRING  
            [1] => "Hello World!"  
            [2] => 2  
        )  
  
    [5] =>   
    [6] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>   
       
            [2] => 2  
        )  
  
    [7] =>   
    [8] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 3  
        )  
  
    [9] => Array  
        (  
            [0] => T_LNUMBER  
            [1] => 1  
            [2] => 3  
        )  
  
    [10] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 3  
        )  
  
    [11] =>   
    [12] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 3  
        )  
  
    [13] => Array  
        (  
            [0] => T_LNUMBER  
            [1] => 1  
            [2] => 3  
        )  
  
    [14] =>   
    [15] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>   
      
            [2] => 3  
        )  
  
    [16] => Array  
        (  
            [0] => T_ECHO  
            [1] => echo  
            [2] => 4  
        )  
  
    [17] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>    
            [2] => 4  
        )  
  
    [18] =>   
    [19] => Array  
        (  
            [0] => T_WHITESPACE  
            [1] =>   
  
            [2] => 4  
        )  
  
    [20] => Array  
        (  
            [0] => T_CLOSE_TAG  
            [1] => ?>  
            [2] => 5  
        )  
  
)  

析这个返回结果我们可以发现,源码中的字符串,字符,空格

都会原样返回。

每个源代码中的字符,都会出现在相应的顺序处。

而其他的,比如标签,操作符,语句,都会被转换成一个包含

部分的

1、Token ID

解释器代号

(也就是在Zend内部的改Token的对应码,比如,T_ECHO,T_STRING)

2、源码中的原来的内容

3、该词在源码中是第几行

Parsing, 将Tokens转换成简单而有意义的表达式

接下来,就是Parsing阶段了,Parsing首先会丢弃Tokens Array中的多于的空格,

然后将剩余的Tokens转换成一个一个的简单的表达式

1.echo a constant string  
2.add two numbers together  
3.store the result of the prior expression to a variable  
4.echo a variable  

Bison是一种通用目的的分析器生成器。它将LALR(1)上下文无关文法的描述转化成分析该文法的C程序。 使用它可以生成解释器,编译器,协议实现等多种程序。 Bison向上兼容Yacc,所有书写正确的Yacc语法都应该可以不加修改地在Bison下工作。 它不但与Yacc兼容还具有许多Yacc不具备的特性。

Bison分析器文件是定义了名为yyparse并且实现了某个语法的函数的C代码。 这个函数并不是一个可以完成所有的语法分析任务的C程序。 除此这外我们还必须提供额外的一些函数: 如词法分析器、分析器报告错误时调用的错误报告函数等等。 我们知道一个完整的C程序必须以名为main的函数开头,如果我们要生成一个可执行文件,并且要运行语法解析器, 那么我们就需要有main函数,并且在某个地方直接或间接调用yyparse,否则语法分析器永远都不会运行。

在PHP源码中,词法分析器的最终是调用re2c规则定义的lex_scan函数,而提供给Bison的函数则为zendlex。 而yyparse被zendparse代替。

  1. Compilation, 将表达式编译成Opocdes

之后就是Compilation阶段了,它会把Tokens编译成一个个op_array, 每个op_arrayd包含如下5个部分
在PHP实现内部,opcode由如下的结构体表如下:

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域则是保存该指令执行完成后的结果。

PHP脚本编译为opcode保存在op_array中,其内部存储的结构如下:

struct _zend_op_array {  
    /* Common elements */  
    zend_uchar type;  
    char *function_name; // 如果是用户定义的函数则,这里将保存函数的名字  
    zend_class_entry *scope;  
    zend_uint fn_flags;  
    union _zend_function *prototype;  
    zend_uint num_args;  
    zend_uint required_num_args;  
    zend_arg_info *arg_info;  
    zend_bool pass_rest_by_reference;  
    unsigned char return_reference;  
    /* END of common elements */  
    zend_bool done_pass_two;  
    zend_uint *refcount;  
    zend_op *opcodes; // opcode数组  
    zend_uint last,size;  
    zend_compiled_variable *vars;  
    int last_var,size_var;  
    // ...  
}  

如上面的注释,opcodes保存在这里,在执行的时候由下面的execute函数执行:

ZEND_API void execute(zend_op_array *op_array TSRMLS_DC)  
{  
    // ... 循环执行op_array中的opcode或者执行其他op_array中的opcode  
}  

前面提到每条opcode都有一个opcode_handler_t的函数指针字段,用于执行该opcode。

PHP有三种方式来进行opcode的处理:CALL,SWITCH和GOTO。

PHP默认使用CALL的方式,也就是函数调用的方式, 由于opcode执行是每个PHP程序频繁需要进行的操作,

可以使用SWITCH或者GOTO的方式来分发, 通常GOTO的效率相对会高一些,

不过效率是否提高依赖于不同的CPU。

  1. Execution,Zend引擎顺次执行Opcodes
    最后一步,也就是Execution,Zend引擎 顺次执行Opcodes,每次一条,从而实现PHP脚本的功能,和机器指令运行相似。

好了,到这里整个PHP代码的执行过程算是写完了,水平有限写的不好还望海涵,有问题希望大家指出。

参考资料以及对他们的致谢(虽然人家不会鸟我们这些小菜。。。):

鸟哥:http://www.laruence.com/2008/06/18/221.html

(注:因为鸟哥的博文是08年的,本文的数据虽然和鸟哥有些相似,PHP发展到现在已经有了不少改变,
所以大家看到鄙人的博文中程序运行结果以及相关的说明与鸟哥的不同,
请不要吃惊,鄙人的结果都是运行验证过的,PHP版本为5.4)
refer
TIPI

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • 转自陈明乾的博客,可能有一定更新。 转原文声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、...
    C86guli阅读 4,677评论 6 72
  • MIME,http,html MIME:MIME(Multipurpose Internet Mail Exten...
    若与阅读 462评论 1 6
  • 目前编程语言可以分为两大类: 第一类是像C/C++, .NET, Java之类的编译型语言, 它们的共性是: 运行...
    石非木阅读 1,463评论 1 2
  • Composer Repositories Composer源 Firegento - Magento模块Comp...
    零一间阅读 3,956评论 1 66
  • 前不久看过《釜山行》,内容很精彩,甚是喜欢。听朋友说这部电影能够跟它相比,带着好奇去欣赏它。 电影中的主要情节是描...
    停不下来的陀螺阅读 634评论 0 0