深入理解PHP原理之PHP脚本执行原理(1)
2016-08-23 20:35
971 查看
PHP是一个被广泛应用的脚本语言,因为它的成功,所以很多时候,我们应用PHP的时候是不需要考虑底层到底是怎么实现的,我相信大多数的PHP程序员是不会去考虑这一点的,在这篇文章中,我会从整个PHP的执行期入手,大致的介绍下各个阶段,包括词法分析、语法分析和opcode。从最初我们编写的PHP脚本->到最后脚本被执行->得到执行结果,这个过程可以分为如下几个阶段:
1. Zend Engine(ZE)调用词法分析器(Lex生成的,源码路径:php/Zend/zend_language_sanner.l), 将我们要执行的PHP源文件去掉空格和注释后,分割成一个个的token;
2. ZE会将得到的token forward给语法分析器(yacc生成, 源码路径:php/Zend/zend_language_parser.y),将Tokens转换成简单而有意义的表达式;
3. ZE会将转换后的表达式,编译为一个个opcode,opcode一般会以op_array的形式存在,它是PHP执行的中间语言。
4. ZE调用zend_executor来执行op_array,输出结果。
下面是PHP脚本执行的整个流程:
![](http://img.blog.csdn.net/20160823210454532)
ZE是一个虚拟机,正是由于它的存在,所以才能使得我们写PHP脚本,完全不需要考虑所在的操作系统类型是什么。ZE是一个CISC(复杂指令处理器),它支持173条指令(源码路径:php/Zend/zend_vm_opcodes.h),包括从最简单的ZEND_ECHO(echo)到复杂的 ZEND_INCLUDE_OR_EVAL(include,require),所有我们编写的PHP都会最终被处理为这173条指令(op code)的序列,从而最终被执行。
上面初步讲解了语法分析、词法分析和PHP脚本执行的整个流程,下面让我看具体看看opcode的结构( 源码路径:php/Zend/zend_compile.h):
该结构有一个标示指令字段zend_op.opcode,以及这个opcode所操作的操作数op1、op2,extended_value字段保存了脚本实际执行的时候可能还需要的其他信息,result字段保存该指令执行完成后的结果,handler字段指明了对应的处理函数,例如如下代码是在编译器遇到print语句的时候进行编译的函数:
当遇到print时,该函数先创建一个zend_op(整个是opcode的最小片段),然后将zend_op.result类型设置为临时变量IS_TMP_VAR,并为该临时变量申请空间,随后指定zend_op.opcode的值为ZEND_PRINT(很奇怪,该值在opcode的指令库中没有查询到),并将传递进来的参数赋值给zend_op.op1,这样在最终执行这条opcode的时候,Zend引擎能获取到对应的信息以便内容输出。
生成的opcode最终会保存在op_array数组中,然后交给Zend处理,op_array的结构如下(源码路径:php/Zend/zend_compile.h):
zend_op_array.opcodes保存了属于这个op_array的opcode数组,zend会根据将zend_op_array作为入参,通过下面的执行函数execute,逐条解释执行每条opcode, 从而实现我们PHP脚本想要的结果。
总结一下,php脚本经过语法分析和词法分析,生产一条条op_line,zend将每条op_line编译成opcode,就产生大量opcode,这些opcode会通过一定形式保存在数组zend_op_array中, 最后以该数组为入参,通过Zend提供的方法execute(zend_op_array *op_array TSRMLS_DC)得到我们想要的结果。
参考1: http://www.php-internals.com/
参考2: http://www.laruence.com/2008/08/11/147.html
1. Zend Engine(ZE)调用词法分析器(Lex生成的,源码路径:php/Zend/zend_language_sanner.l), 将我们要执行的PHP源文件去掉空格和注释后,分割成一个个的token;
2. ZE会将得到的token forward给语法分析器(yacc生成, 源码路径:php/Zend/zend_language_parser.y),将Tokens转换成简单而有意义的表达式;
3. ZE会将转换后的表达式,编译为一个个opcode,opcode一般会以op_array的形式存在,它是PHP执行的中间语言。
4. ZE调用zend_executor来执行op_array,输出结果。
下面是PHP脚本执行的整个流程:
ZE是一个虚拟机,正是由于它的存在,所以才能使得我们写PHP脚本,完全不需要考虑所在的操作系统类型是什么。ZE是一个CISC(复杂指令处理器),它支持173条指令(源码路径:php/Zend/zend_vm_opcodes.h),包括从最简单的ZEND_ECHO(echo)到复杂的 ZEND_INCLUDE_OR_EVAL(include,require),所有我们编写的PHP都会最终被处理为这173条指令(op code)的序列,从而最终被执行。
上面初步讲解了语法分析、词法分析和PHP脚本执行的整个流程,下面让我看具体看看opcode的结构( 源码路径:php/Zend/zend_compile.h):
struct _zend_op { opcode_handler_t handler; // 执行该opcode时调用的处理函数 znode result; // 执行完后的返回结果 znode op1; // 操作数1 znode op2; // 操作数2 ulong extended_value; // 执行时可能还需要用到的其它信息 uint lineno; zend_uchar opcode; // opcode代码 };
该结构有一个标示指令字段zend_op.opcode,以及这个opcode所操作的操作数op1、op2,extended_value字段保存了脚本实际执行的时候可能还需要的其他信息,result字段保存该指令执行完成后的结果,handler字段指明了对应的处理函数,例如如下代码是在编译器遇到print语句的时候进行编译的函数:
void zend_do_print(znode *result,const znode *arg TSRMLS_DC) { zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); opline->result.op_type = IS_TMP_VAR; opline->result.u.var = get_temporary_variable(CG(active_op_array)); opline->opcode = ZEND_PRINT; opline->op1 = *arg; SET_UNUSED(opline->op2); *result = opline->result; }
当遇到print时,该函数先创建一个zend_op(整个是opcode的最小片段),然后将zend_op.result类型设置为临时变量IS_TMP_VAR,并为该临时变量申请空间,随后指定zend_op.opcode的值为ZEND_PRINT(很奇怪,该值在opcode的指令库中没有查询到),并将传递进来的参数赋值给zend_op.op1,这样在最终执行这条opcode的时候,Zend引擎能获取到对应的信息以便内容输出。
生成的opcode最终会保存在op_array数组中,然后交给Zend处理,op_array的结构如下(源码路径:php/Zend/zend_compile.h):
struct _zend_op_array { /* Common elements */ zend_uchar type; zend_uchar arg_flags[3]; uint32_t fn_flags; zend_string *function_name; zend_class_entry *scope; zend_function *prototype; uint32_t num_args; uint32_t required_num_args; zend_arg_info *arg_info; /* END of common elements */ uint32_t *refcount; uint32_t this_var; uint32_t last; zend_op *opcodes; int last_var; uint32_t T; zend_string **vars; int last_brk_cont; int last_try_catch; zend_brk_cont_element *brk_cont_array; zend_try_catch_element *try_catch_array; /* static variables support */ HashTable *static_variables; zend_string *filename; uint32_t line_start; uint32_t line_end; zend_string *doc_comment; uint32_t early_binding; /* the linked list of delayed declarations */ int last_literal; zval *literals; int cache_size; void **run_time_cache; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; };
zend_op_array.opcodes保存了属于这个op_array的opcode数组,zend会根据将zend_op_array作为入参,通过下面的执行函数execute,逐条解释执行每条opcode, 从而实现我们PHP脚本想要的结果。
ZEND_API void execute(zend_op_array *op_array TSRMLS_DC) { // ... 循环执⾏op_array中的opcode或者执⾏其他op_array中的opcode }
总结一下,php脚本经过语法分析和词法分析,生产一条条op_line,zend将每条op_line编译成opcode,就产生大量opcode,这些opcode会通过一定形式保存在数组zend_op_array中, 最后以该数组为入参,通过Zend提供的方法execute(zend_op_array *op_array TSRMLS_DC)得到我们想要的结果。
参考1: http://www.php-internals.com/
参考2: http://www.laruence.com/2008/08/11/147.html
相关文章推荐
- 简评PHP引用语法的设计
- thinkphp多表操作简记
- php以数组形式获得配置文件数据示例详解
- 10高级PHP提示要提高你的编程
- 服务器搭建2 VSFTP搭建FTP服务器
- PHP 性能优化
- PHP empty、isset、isnull的区别
- php 导入excel解析提示 Fatal error: Class 'ZipArchive' not found解决方法
- PHP自定义函数获取URL中一级域名的方法
- 深入理解PHP7之zval
- liunx 定时执行 php文件
- PHP可逆加密解密算法
- Notice: unserialize(): Error at offset 134526789 of 2136547489 bytes in file.php on line 130
- 利用commons-net-3.3,实现ftp的文件下载功能
- php中的trait
- PHP 7中新的Hashtable实现和性能改进
- PHP与C#通用 DES 加解密
- PHP编程效率的20个要点-[转]
- Windows Bat 用FTP上传文件 使用笔记
- PHP 上传多个文件