深入浅出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脚本想要的结果。
首先,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脚本想要的结果。
相关文章推荐
- ThinkPHP3.1.3部署到SAE 教程
- PHP中二维数组排序问题
- 推荐25款php中非常有用的类库
- 使用JDBC连接Mysql数据库存入二进制图片并实现读取在PHP中打开展示
- php大力力 [009节]php在百度文库的几个基础教程
- PHP 中this,self,parent的区别
- 解析php中die(),exit(),return的区别
- php set_error_handler() 重要用法
- php系统常量
- php中的变量详解
- php中的数据类型详解
- Atitit.加密算法 des  aes 各个语言不同的原理与解决方案java php c#
- Atitit.加密算法 des aes 各个语言不同的原理与解决方案java php c#
- win7+IIS+FastCGI+php+mysql
- php大力力 [008节]局部表量
- InputStream与OutputStream的比较
- PHP到MySQL数据查询过程概述
- ContentProvider和Uri
- php大力力 [007节]php静态表量
- php get_class_vars函数