您的位置:首页 > 编程语言 > PHP开发

深入浅出PHP(Exploring PHP)

2015-08-23 20:50 756 查看
从最初我们编写的PHP脚本->到最后脚本被执行->得到执行结果,这个过程,其实可以分为如下几个阶段:
首先,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.‘ thathas 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:
1.       ZEND_API
externvoid (*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_name为NULL。结构中的opcodes保存了属于这个op_array的op
code数组,zend_execute会从start_op开始,逐条解释执行传入的每条op
code, 从而实现我们PHP脚本想要的结果。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: