msgbartop
PHP语言, PHP扩展, Zend引擎相关的研究,技术,新闻分享 – 左手代码 右手诗
msgbarbottom

11 Aug 08 深入浅出PHP(Exploring PHP)

一直以来,横观国内的PHP现状,很少有专门介绍PHP内部机制的书。呵呵,我会随时记录下研究的心得,有机会的时候,汇总成书。:)

今天这篇,我内心是想打算做为一个导论:

PHP是一个被广泛应用的脚本语言,因为它的成功,所以很多时候,我们应用PHP的时候是更不不需要考虑底层到底是怎么实现的。我相信大多数的PHP程序 员是不会去考虑这一点的。从我接触PHP开始,到今天也就是3年,这三年里,前俩年我一直都是在”用”PHP,每次写出来一段脚本,我就会想“恩,不用担 心,PHP解释器会知道我想做什么的”,直到去年来到雅虎,接受了一个工作,是做一个PHP的Extension,从这个时候开始,我就好奇于新接触的一 大堆的新鲜事物,zend, TSRM, zval, hashtable, op_array…

于是我到处查阅资料,每次获得一篇好的文章,或者一段好的文字我就会如获珍宝,打印保存起来,细细研读。我发现,国内关于PHP内部的资料真是少的可怜, 不知道是因为懂得的人多但是不愿意分享,还是懂得的人本来就少,所以,这条路,我走的很辛苦。于是,就会有了这篇文章。

在这篇文章中,我会从整个PHP的执行期入手,大致的介绍下各个阶段,词法分析,语法分析,op code等等,以后的文章我会再详细介绍每个阶(当然,如果你急不可耐的想知道详细,呵呵,那么可以直接联系我)。

从最初我们编写的PHP脚本->到最后脚本被执行->得到执行结果,这个过程,其实可以分为如下几个阶段(鄙视:CSDN不能上图):

首先,Zend Engine(ZE),调用词法分析器(Lex生成的,源文件在 Zend/zend_language_sanner.l), 将我们要执行的PHP源文件,去掉空格 ,注释,分割成一个一个的token。

然后,ZE会将得到的token forward给语法分析器(yacc生成, 源文件在 Zend/zend_language_parser.y),生成一个一个的op code,opcode一般会以op array的形式存在,它是PHP执行的中间语言。

最后,ZE调用zend_executor来执行op array,输出结果。


图1 处理流程

ZE是一个虚拟机,正是由于它的存在,所以才能使得我们写PHP脚本,完全不需要考虑所在的操作系统类型是什么。ZE是一个CISC(复杂指令处理器), 它支持150条指令(具体指令在 Zend/zend_vm_opcodes.h),包括从最简单的ZEND_ECHO(echo)到复杂的 ZEND_INCLUDE_OR_EVAL(include,require),所有我们编写的PHP都会最终被处理为这150条指令(op code)的序列,从而最终被执行。

那有什么办法可以看到我们的PHP脚本,最终被“翻译”成什么样的呢? 也就是说,op code张的什么样子呢? 呵呵,达到这个,我们需要重新编译PHP,修改它的compile_file和zend_execute函数。不过,在PECL中已经有这样的模块,可以 让我们直接使用了,那就是由 Derick Rethans开发的VLD (Vulcan Logic Dissassembler)模块。你只要下载这个模块,并把他载入PHP中,就可以通过简单的设置,来得到脚本翻译的结果了。具体关于这个模块的使用说 明-雅虎一下,你就知道^_^。

接下来,让我们尝试用VLD来查看一段简单的PHP脚本的中间语言。

原始代码:

<?php

$i = This is a string;

//I am comments

echo $i. that has been echoed to screen;

?>

采用VLD得到的op codes:

filename:/home/Desktop/vldOutOne.php

function name: (null)

number of ops: 7

line #  op                 fetch       ext  operands

——————————————————————————————————————————-

2 0 FETCH_W local $0, i

1 ASSIGN $0, This+is+a+string

4 2 FETCH_R local $2, i

3 CONCAT ~3, $2,+that+has+been+echoed+to+screen

4 ECHO ~3

6 5 RETURN 1

6 ZEND_HANDLE_EXCEPTION

我们可以看到,源文件中的注释,在op code中,已经没有了,所以不用担心注释太多会影响你的脚本执行时间(实际上,它是会影响ZE的词法处理阶段的用时而已)。

现在我们来一条一条的分析这段op codes,每一条op code 又叫做一条op_line,都由如下7个部分,在zend_compile.h中,我们可以看到如下定义:

struct _zend_op {

opcode_handler_t handler;

znode result;

znode op1;

znode op2;

ulong extended_value;

uint lineno;

zend_uchar opcode;

};

其中,opcode字段指明了这操作类型,handler指明了处理器,然后有俩个操作数,和一个操作结果。

  1. FETCH_W, 是以写的方式获取一个变量,此处是获取变量名”i”的变量于$0(*zval)。
  2. 将字符串”this+is+a+string”赋值(ASSIGN)给$0
  3. 字符串连接
  4. 显示

可以看出,这个很类似于很多同学大学学习编译原理时候的三元式,不同的是,这些中间代码会被Zend VM(Zend虚拟机)直接执行。

真正负责执行的函数是,zend_execute, 查看zend_execute.h:

ZEND_API extern void (*zend_execute)(zend_op_array *op_array TSRMLS_DC);

可以看出, zend_execute接受zend_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_uint *refcount;

    zend_op *opcodes;
    zend_uint last, size;

    zend_compiled_variable *vars;
    int last_var, size_var;

    zend_uint T;

    zend_brk_cont_element *brk_cont_array;
    zend_uint last_brk_cont;
    zend_uint current_brk_cont;

    zend_try_catch_element *try_catch_array;
    int last_try_catch;

    /* static variables support */
    HashTable *static_variables;

    zend_op *start_op;
    int backpatch_count;

    zend_bool done_pass_two;
    zend_bool uses_this;

    char *filename;
    zend_uint line_start;
    zend_uint line_end;
    char *doc_comment;
    zend_uint doc_comment_len;

    void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};

可以看到,zend_op_array的结构和zend_function的结构很像(参看我的其他文章), 对于在全局作用域的代码,就是不包含在任何function内的op_array,它的function_name为NULL。结构中的opcodes保存了属于这个op_array的op code数组,zend_execute会从start_op开始,逐条解释执行传入的每条op code, 从而实现我们PHP脚本想要的结果。

下一次,我将介绍PHP变量的灵魂 – zval, 你将会看到PHP是如何实现它的变量传递,类型戏法,等等。


分享到:



Related Posts:

Tags: , , ,

33 Responses to “深入浅出PHP(Exploring PHP)”

  1. 深入浅出PHP(Exploring PHP) – lampNick |

    [...] 原文地址: http://www.laruence.com/2008/08/11/147.html [...]

  2. shyandsy |

    看到这篇文章时候已经173条指令了

  3. (转) 深入理解PHP原理之变量(Variables inside PHP) - 码农@师帅 |

    [...] 深入浅出PHP(PHP Internals) [...]

  4. 深入了解php变量内核原理及变量内存管理系列文章 | pitaya's blog | 专注与LAMP很多年,Nginx,Linux,PHP,MySQL,Apache,Redis,Memcache,MongoDB,Oracle,Jquery,CentOS,Hadoop,weibo.com |

    [...] 主要看鸟哥的博客就行,但是要顺序渐进,按以下顺序研究行啦。 1、深入浅出PHP(Exploring PHP) http://www.laruence.com/2008/08/11/147.html 2、深入理解PHP原理之Opcodes [...]

  5. 用vld查看opcode » hilojack |

    [...] opcode是php的中间语言.(要理解php的执行机制可以戳鸟哥的深入理解php) 而如何查看opcode?那就是vld(Vulcan Logic Dumper)这款php扩展了。默认的php是没有提供这一扩展的,本文所要描述的是如何安装vld。我的环境是archlinux,别的环境类似(路径会有差异) [...]

  6. php 底层 探究之 php弱类型的实现 | Buddy Laboratory |

    [...] 深入浅出PHP(PHP Internals) [...]

  7. 编码者说 » Blog Archive » 如何为PHP贡献代码 - 为web开发者而建 |

    [...] 深入浅出PHP(Exploring PHP) « PHP的历史 [...]

  8. 深入浅出PHP(Exploring PHP) | warpig_allen's blog |

    [...] 本文地址: http://www.laruence.com/2008/08/11/147.html [...]

  9. 深入浅出PHP(Exploring PHP) | 万维网黑客联盟 |

    [...] 本文地址: http://www.laruence.com/2008/08/11/147.html [...]

  10. 怪异的js编码问题 | bjmayor的个人空间 |

    [...] 深入浅出PHP(Exploring PHP) [...]

  11. xiaobao |

    上面那张图对吗,鸟哥?
    require或者函数是编译的时候一并编译了,还是等到执行的时候,再回头去编译?好像应该是前者吧。

  12. google |

    翻山越岭终于找到这篇好文章

  13. mahone |

    不是计算机专业,没学过编译原理和汇编,看不懂文章……可悲……

  14. mahone |

    非常可惜啊,不是计算机专业,编译原理和汇编都没学过……悲剧啊……看不懂文章

  15. 人海孤鸿 |

  16. phper |

    博主的文章写得太好了!偶像,我要长期关注

  17. xiaokai |

    很是深奥, 但是总一天我会弄懂的..

  18. zhifu |

    强人,我也做了快一年的PHPER,最近在写项目,总是遇到很棘手的问题,导致最后学得去研究底层,现在才发现,要想把技术做好,真得研究底层。

    还是很感谢,楼主有这样的精力去研究,并开源给我们,小弟真的十分感谢呀

  19. 不是什么区 |

    不错,既然有现成了我就懒得写了,不介意我转走了吧?

  20. dyh1919 |

    编译原理没学好,看不懂

  21. coolboy0316 |

    太深奥了,不是很懂.
    学习….

  22. guangtianxia |

    支持这种精神。

  23. smilesoul |

    老大啊 你是我的偶像啊 你怎么学这么深 佩服佩服!

    能不能指点下怎么学写一个php的c扩展

    需要具备学习哪些知识,能不能谈谈经验

  24. highjade |

    学习~

  25. sue |

    传说中的“右手诗”在哪里啊。。

  26. glemir |

    冰之河说:“要坚持,俗话说:一个人,吟一首诗并不难;难的是,吟一辈子诗”
    xinquan的目标是要吟一辈子“唐诗”。

  27. liexusong |

    你出PHP内核的书的话,我一定买!!

  28. Leftwater |

    决定长期阅读学习博主文章~

  29. ylcz |

    太深奥了

  30. lffly |

    写的不错。订阅了你的博客

  31. yongtao.pang |

    看zval去啦,有人劈荆斩棘真好

  32. 雪候鸟 |

    恩,有功夫我就写点,呵呵;)

  33. 冰的河 |

    不错,尤其是右手代码,左手诗,很赞。要坚持,俗话说:一个人,吟一首诗并不难;难的是,吟一辈子诗。

  34. xinquan |

    en, not bad … continue …

Leave a Reply

*