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

深入理解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脚本执行的整个流程:

  


  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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: