深入浅出PHP(Exploring PHP)

来源:互联网 发布:json字符串去掉双引号 编辑:程序博客网 时间:2024/05/02 23:40

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

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

然后,ZE会将得到的token forward给语法分析器(yacc生成,源文件在Zend/zend_language_parser.y),生成一个一个的op codeopcode一般会以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_filezend_execute函数。不过,在PECL中已经有这样的模块,可以让我们直接使用了,那就是由 Derick Rethans开发的VLD (Vulcan Logic Dissassembler)模块。你只要下载这个模块,并把他载入PHP中,就可以通过简单的设置,来得到脚本翻译的结果了。具体关于这个模块的使用说明-雅虎一下,你就知道^_^

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

原始代码:

<?php

$i = This is a string;

//I am comments

echo $i. thathas been echoed to screen;

?>

采用VLD得到的op codes:

filename:/home/Desktop/vldOutOne.php

function name(null)

number of ops7

line #  op                fetch       ext  operands

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

2 0 FETCH_W local $0i

1 ASSIGN $0This+is+a+string

4 2 FETCH_R local $2i

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 VMZend虚拟机)直接执行。

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

1.       ZEND_APIexternvoid (*zend_execute)(zend_op_array *op_array TSRMLS_DC);

可以看出, zend_execute接受zend_op_array*作为参数。

1.        struct _zend_op_array {

2.           /* Common elements */

3.           zend_uchar type;

4.           char *function_name;

5.           zend_class_entry *scope;

6.           zend_uint fn_flags;

7.           union _zend_function *prototype;

8.           zend_uint num_args;

9.           zend_uint required_num_args;

10.        zend_arg_info *arg_info;

11.        zend_bool pass_rest_by_reference;

12.        unsigned char return_reference;

13.        /* END of common elements */

14.     

15.        zend_uint *refcount;

16.     

17.        zend_op *opcodes;

18.        zend_uint last, size;

19.     

20.        zend_compiled_variable *vars;

21.        int last_var, size_var;

22.     

23.        zend_uint T;

24.     

25.        zend_brk_cont_element *brk_cont_array;

26.        zend_uint last_brk_cont;

27.        zend_uint current_brk_cont;

28.     

29.        zend_try_catch_element *try_catch_array;

30.        int last_try_catch;

31.     

32.        /* static variables support */

33.        HashTable *static_variables;

34.     

35.        zend_op *start_op;

36.        int backpatch_count;

37.     

38.        zend_bool done_pass_two;

39.        zend_bool uses_this;

40.     

41.        char *filename;

42.        zend_uint line_start;

43.        zend_uint line_end;

44.        char *doc_comment;

45.        zend_uint doc_comment_len;

46.     

47.        void *reserved[ZEND_MAX_RESERVED_RESOURCES];

48.    };

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

0 0
原创粉丝点击