PHP内核学习课程之php opcode内核查现_php实例_脚本之家

PHP 是一门解释型的语言。诸如 Java、Python、Ruby、Javascript
等解释型语言,大家编辑的代码不会被编写翻译成机器码运营,而是会被编写翻译中间码运维在设想机(VM)上。运转PHP 的设想机,称之为 Zend 设想机,后天我们将深切内核,切磋 Zend
设想机械运输转的原理。

初藳链接:

1.

先说个PHP5.3+ 的语法糖,常常大家那样写:

<?php
    $a = 0;
    $b = $a ? $a : 1;

语法糖能够如此写:

<?php
    $a = 0;
    $b = $a ?: 1;

实施结果$b =
1,前边写法更简洁,但日常不太提议用太多语法糖,特别是轻巧驾驭混淆的,比如PHP
7 新扩大??如下:

<?php
    $b = $a ?? 1;

相当于:

<?php
    $b = isset($a) ? $a : 1;

?: 和 ??
你是还是不是轻巧搞混,即使如此,小编建议宁可不要,代码可读性强,易维护更首要。

语法糖不是本文的珍视,大家的指标是从语法糖入手聊聊Zend VM的深入分析原理。

opcode是计算机指令中的后生可畏都部队分,用于钦命要推行的操作,
指令的格式和标准由Computer的下令标准钦定。
除了命令自己以外平时还也有指令所急需的操作数,恐怕有的指令没有需求显式的操作数。
那么些操作数大概是存放器中的值,客栈中的值,某块内部存款和储蓄器的值大概IO端口中的值等等。

OPCODE

怎么是 OPCODE?它是后生可畏种设想机能够分辨并拍卖的通令。Zend
设想机包括了风姿洒脱连串的 OPCODE,通过 OPCODE 设想机能够做过多事情,列举多少个OPCODE 的例子:

  • ZEND_ADD 将五个操作数相加。
  • ZEND_NEW 创立一个 PHP 对象。
  • ZEND_ECHO 将内容输出到正规输出中。
  • ZEND_EXIT 退出 PHP。

如此那般的操作,PHP 定义了1捌十六个(随着 PHP 的更新,分明会支撑更加的多花色的
OPCODE),全数的 OPCODE
的定义和落到实处都能够在源码的 zend/zend_vm_def.h 文件(那么些文件的剧情并非原生的
C 代码,而是三个模板,前边会表达原因)中查阅到。

咱俩来看下 PHP 是何等规划 OPCODE 数据构造:

struct _zend_op {
    const void *handler;
    znode_op op1;
    znode_op op2;
    znode_op result;
    uint32_t extended_value;
    uint32_t lineno;
    zend_uchar opcode;
    zend_uchar op1_type;
    zend_uchar op2_type;
    zend_uchar result_type;
};

稳重观望 OPCODE 的数据构造,是还是不是能找到汇编语言的感到。每一个 OPCODE
都富含八个操作数,op1和 op2handler 指针则指向了实践该 OPCODE
操作的函数,函数处理后的结果,会被封存在 result 中。

咱俩举一个简单易行的事例:

<?php
$b = 1;
$a = $b + 2;

小编们透过 vld 扩充见到,经过编写翻译的后,上边的代码生成了 ZEND_ADD 指令的
OPCODE。

compiled vars:  !0 = $b, !1 = $a
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 1
   3     1        ADD                                              ~3      !0, 2
         2        ASSIGN                                                   !1, ~3
   8     3      > RETURN                                                   1

里面,第二行是 ZEND_ADD 指令的
OPCODE。大家见到,它接收2个操作数,op1 是变量 $bop2 是数字常量1,再次来到的结果存入了临时变量中。在 zend/zend_vm_def.h 文件中,大家能够找到
ZEND_ADD 指令对应的函数实现:

ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMPVAR|CV, CONST|TMPVAR|CV)
{
    USE_OPLINE
    zend_free_op free_op1, free_op2;
    zval *op1, *op2, *result;

    op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
    op2 = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
    if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_LONG)) {
        if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_LONG)) {
            result = EX_VAR(opline->result.var);
            fast_long_add_function(result, op1, op2);
            ZEND_VM_NEXT_OPCODE();
        } else if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_DOUBLE)) {
            result = EX_VAR(opline->result.var);
            ZVAL_DOUBLE(result, ((double)Z_LVAL_P(op1)) + Z_DVAL_P(op2));
            ZEND_VM_NEXT_OPCODE();
        }
    } else if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_DOUBLE)) {

    ...
}

地方的代码而不是原生的 C 代码,而是生龙活虎种模板。

为何那样做?因为 PHP 是弱类型语言,而其达成的 C
则是强类型语言。弱类型语言扶持活动类型匹配,而活动类型相称的兑现方式,就像上述代码同样,通过判别来拍卖区别类其他参数。试想一下,若是各类OPCODE
处理的时候都亟待看清传入的参数类型,那么品质势必成为高大的标题(一回呼吁需求处理的
OPCODE 或许能落得不计其数个)。

哪有何办法吗?大家发以往编写翻译的时候,已经能够规定每一种操作数的门类(只怕是常量照旧变量)。所以,PHP
真正施行时的 C
代码,分歧体系操作数将分成不相同的函数,供设想机直接调用。那有个别代码放在了 zend/zend_vm_execute.h 中,打开后的文书非常大,并且我们注意到还也可以有这么的代码:

if (IS_CONST == IS_CV) {

一同未有啥样含义是啊?可是并未有关联,C
的编写翻译器会自动优化那样判别。大好多情状,大家盼望理解有个别 OPCODE
管理的逻辑,如故通过阅读模板文件 zend/zend_vm_def.h 相比比较容易于。顺便说一下,依照模板生成
C 代码的程序正是用 PHP 完成的。

原文:

2.

深入深入分析的PHP源码分支 =>
remotes/origin/PHP-5.6.14,关于怎么样通过vld查看opcode,请看本身从前写的那篇文章:

<?php
    $a = 0;
    $b = $a ?: 1;

对应的opcdoe如下:

number of ops:  5
compiled vars:  !0 = $a, !1 = $b
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 0
   3     1        JMP_SET_VAR                                      $1      !0
         2        QM_ASSIGN_VAR                                    $1      1
         3        ASSIGN                                                   !1, $1
   4     4      > RETURN                                                   1

branch: #  0; line:     2-    4; sop:     0; eop:     4; out1:  -2
path #1: 0,

vim Zend/zend_language_parser.y +834

834 ›   |›  expr '?' ':' { zend_do_jmp_set(&$1, &$2, &$3 TSRMLS_CC); }
835 ›   ›   expr     { zend_do_jmp_set_else(&$$, &$5, &$2, &$3 TSRMLS_CC); }

设若您喜爱,能够团结出手,重新定义 ?:
的语法糖。坚决守住BNF文法则则,使用bison分析,风野趣能够活动Google相关知识,继续深切摸底。

从vld的opcode能够精通,推行了 zend_do_jmp_set_else,代码在
Zend/zend_compile.c 中:

void zend_do_jmp_set_else(znode *result, const znode *false_value, const znode *jmp_token, const znode *colon_token TSRMLS_DC)
{
›   zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

›   SET_NODE(opline->result, colon_token);
›   if (colon_token->op_type == IS_TMP_VAR) {
›   ›   if (false_value->op_type == IS_VAR || false_value->op_type == IS_CV) {
›   ›   ›   CG(active_op_array)->opcodes[jmp_token->u.op.opline_num].opcode = ZEND_JMP_SET_VAR;
›   ›   ›   CG(active_op_array)->opcodes[jmp_token->u.op.opline_num].result_type = IS_VAR;
›   ›   ›   opline->opcode = ZEND_QM_ASSIGN_VAR;
›   ›   ›   opline->result_type = IS_VAR;
›   ›   } else {
›   ›   ›   opline->opcode = ZEND_QM_ASSIGN;
›   ›   }
›   } else {
›   ›   opline->opcode = ZEND_QM_ASSIGN_VAR;
›   }
›   opline->extended_value = 0;
›   SET_NODE(opline->op1, false_value);
›   SET_UNUSED(opline->op2);

›   GET_NODE(result, opline->result);

›   CG(active_op_array)->opcodes[jmp_token->u.op.opline_num].op2.opline_num = get_next_op_number(CG(active_op_array));

›   DEC_BPC(CG(active_op_array));
}

经常opcode还会有另后生可畏种称谓:字节码。
举例Java设想机,.NET的通用中间语言(CIL: Common Intermeditate
Language卡塔尔(قطر‎等等。

实行进度

正确的来讲,PHP
的实行分成了两大学一年级些:编译和实施。这里笔者将不会详细张开编写翻译的片段,而是把核心放在实行的历程。

经过语法、词法解析等大器晚成层层的编写翻译进程后,大家拿到了三个名称叫 OPArray
的数量,其组织如下:

struct _zend_op_array {
    /* Common elements */
    zend_uchar type;
    zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
    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 last;
    zend_op *opcodes;

    int last_var;
    uint32_t T;
    zend_string **vars;

    int last_live_range;
    int last_try_catch;
    zend_live_range *live_range;
    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];
};

剧情非常多对啊?轻便的知晓,其本质正是一个 OPCODE
数组外加试行进程中所必要的条件数据的集中。介绍多少个相对来讲相比较首要的字段:

  • opcodes 存放 OPCODE 的数组。
  • filename 当前实践的台本的文书名。
  • function_name 当前推行的法子名称。
  • static_variables 静态变量列表。
  • last_try_catch try_catch_array 当前上下文中,假使现身异常try-catch-finally 跳转所需的信息。
  • literals 全体诸如字符串 foo 可能数字23,那样的常量字面量群集。

为什么供给转换那样庞大的多寡?因为编译时期生成的消息越来越多,施行时代所要求的光阴就越少。

接下去,大家看下 PHP 是如何推行 OPCODE。OPCODE
的试行被放在叁个循环中,这些轮回坐落于 zend/zend_vm_execute.h 中的 execute_ex 函数:

ZEND_API void execute_ex(zend_execute_data *ex)
{
    DCL_OPLINE

    zend_execute_data *execute_data = ex;

    LOAD_OPLINE();
    ZEND_VM_LOOP_INTERRUPT_CHECK();

    while (1) {
        if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) {
            if (EXPECTED(ret > 0)) {
                execute_data = EG(current_execute_data);
                ZEND_VM_LOOP_INTERRUPT_CHECK();
            } else {
                return;
            }
        }
    }

    zend_error_noreturn(E_CORE_ERROR, "Arrived at end of main loop which shouldn't happen");
}

这里,小编去掉了某些景况变量判定分支,保留了运转的主流程。能够看见,在贰个极端循环中,虚构时机不断调用
OPCODE
钦定的 handler 函数管理指令集,直到某次指令处理的结果 ret 小于0。注意到,在主流程中并不曾挪动
OPCODE
数组的眼下线指挥部针,而是把那一个历程置于指令施行的切实函数的最后。所以,大家在大多数OPCODE 的得以达成函数的终极,都能看出调用那一个宏:

ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();

在事情未发生前非常轻巧例子中,大家见到 vld 打字与印刷出的实施 OPCODE
数组中,最终有大器晚成项命令为 ZEND_RETURN 的 OPCODE。但我们编辑的 PHP
代码中并未那样的言语。在编写翻译时代,设想机遇自动将以此命令加到 OPCODE
数组的尾声。ZEND_RETURN 指令对应的函数会回到
-1,判别履行的结果小于0时,就能脱离循环,进而截至程序的运转。

    

3.

驷比不上舌三个opcode,ZEND_JMP_SET_VAR 和
ZEND_QM_ASSIGN_VAOdyssey,怎么接着读代码呢?上面说下PHP的opcode。

PHP5.6有1陆二十个opcode,意味着能够实施167种分裂的计量操作,官方文档看这里

PHP内部使用_zend_op 那些布局体来表示opcode, vim Zend/zend_compile.h
+111

111 struct _zend_op {
112 ›   opcode_handler_t handler;
113 ›   znode_op op1;
114 ›   znode_op op2;
115 ›   znode_op result;
116 ›   ulong extended_value;
117 ›   uint lineno;
118 ›   zend_uchar opcode;
119 ›   zend_uchar op1_type;
120 ›   zend_uchar op2_type;
121 ›   zend_uchar result_type;
122 }

PHP 7.0略有不一致,首要不同在针对陆拾四位系统
uint换来uint32_t,鲜明钦命字节数。

您把opcode当成一个总计器,只选择八个操作数(op1,
op2卡塔尔国,实施三个操作(handler,
举例加减乘除卡塔尔国,然后它回到一个结出(resultState of Qatar给你,再稍加拍卖算术溢出的境况(extended_value)。

Zend的VM对种种opcode的办事措施完全相似,都有贰个handler(函数指针),指向管理函数的地址。那是多少个C函数,包含了试行opcode对应的代码,使用op1,op2做为参数,实施到位后,会回去贰个结果(result),有时也会叠合风姿罗曼蒂克段新闻(extended_value)。

用我们例子中的操作数 ZEND_JMP_SET_VAR 说明,vim Zend/zend_vm_def.h
+4995

4942 ZEND_VM_HANDLER(158, ZEND_JMP_SET_VAR, CONST|TMP|VAR|CV, ANY)
4943 {
4944 ›   USE_OPLINE
4945 ›   zend_free_op free_op1;
4946 ›   zval *value, *ret;
4947
4948 ›   SAVE_OPLINE();
4949 ›   value = GET_OP1_ZVAL_PTR(BP_VAR_R);
4950
4951 ›   if (i_zend_is_true(value)) {
4952 ›   ›   if (OP1_TYPE == IS_VAR || OP1_TYPE == IS_CV) {
4953 ›   ›   ›   Z_ADDREF_P(value);
4954 ›   ›   ›   EX_T(opline->result.var).var.ptr = value;
4955 ›   ›   ›   EX_T(opline->result.var).var.ptr_ptr = &EX_T(opline->result.var).var.ptr;
4956 ›   ›   } else {
4957 ›   ›   ›   ALLOC_ZVAL(ret);
4958 ›   ›   ›   INIT_PZVAL_COPY(ret, value);
4959 ›   ›   ›   EX_T(opline->result.var).var.ptr = ret;
4960 ›   ›   ›   EX_T(opline->result.var).var.ptr_ptr = &EX_T(opline->result.var).var.ptr;
4961 ›   ›   ›   if (!IS_OP1_TMP_FREE()) {
4962 ›   ›   ›   ›   zval_copy_ctor(EX_T(opline->result.var).var.ptr);
4963 ›   ›   ›   }
4964 ›   ›   }
4965 ›   ›   FREE_OP1_IF_VAR();
4966 #if DEBUG_ZEND>=2
4967 ›   ›   printf("Conditional jmp to %d\n", opline->op2.opline_num);
4968 #endif
4969 ›   ›   ZEND_VM_JMP(opline->op2.jmp_addr);
4970 ›   }
4971
4972 ›   FREE_OP1();
4973 ›   CHECK_EXCEPTION();
4974 ›   ZEND_VM_NEXT_OPCODE();
4975 }

i_zend_is_true
来推断操作数是不是为true,所以ZEND_JMP_SET_VA卡宴是生龙活虎种口径赋值,相信我们都能看掌握,下边讲重视。

注意zend_vm_def.h那实际不是叁个得以直接编写翻译的C的头文件,只好算得七个模板,具体可编译的头为zend_vm_execute.h(这几个文件可有45000多行哦),它并不是手动生成,而是由zend_vm_gen.php其生机勃勃PHP脚本深入解析zend_vm_def.h后变卦(风趣啊,先有鸡依然先有蛋,未有PHP
哪来的这几个剧本?),估摸这几个是中期付加物,前期php版本应该不会用那几个。

上面ZEND_JMP_SET_VA大切诺基的代码,依照不相同参数 CONST|TMP|VAR|CV
末了会转换差异类型的,但职能相同的handler函数:

static int ZEND_FASTCALL  ZEND_JMP_SET_VAR_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
static int ZEND_FASTCALL  ZEND_JMP_SET_VAR_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
static int ZEND_FASTCALL  ZEND_JMP_SET_VAR_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
static int ZEND_FASTCALL  ZEND_JMP_SET_VAR_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

那般做的指标是为了在编写翻译期鲜明handler,提高运营期的属性。不那样做,在运转期依照参数类型选用,也得以造成,但质量不佳。当然如此做有的时候也会扭转一些杂质代码(看似无用),不用操心,C的编写翻译器会更为优化管理。

zend_vm_gen.php 也能够承担一些参数,细节在PHP源码中的README文件
Zend/README.ZEND_VM 有详细表达。

1. Opcode简介

主意调用

假定大家调用三个自定义的函数,虚构机遇怎么管理呢?

<?php
function foo() {
    echo 'test';
}

foo();

大家透过 vld 查看生成的 OPCODE。现身了三个 OPCODE
指令实行栈,是因为我们自定义了四个 PHP
函数。在首先个实践栈上,调用自定义函数会推行两个 OPCODE
指令:INIT_FCALL 和 DO_FCALL

compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   NOP
   6     1        INIT_FCALL                                               'foo'
         2        DO_FCALL                                      0
         3      > RETURN                                                   1

compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   3     0  E >   ECHO                                                     'test'
   4     1      > RETURN                                                   null

其中,INIT_FCALL 筹算了实行函数时所急需的上下文数据。DO_FCALL 负担实行函数。DO_FCALL 的管理函数依照分裂的调用情形管理了汪洋逻辑,笔者采用了在那之中举行客商定义的函数的逻辑部分:

ZEND_VM_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL))
{
    USE_OPLINE
    zend_execute_data *call = EX(call);
    zend_function *fbc = call->func;
    zend_object *object;
    zval *ret;

    ...

    if (EXPECTED(fbc->type == ZEND_USER_FUNCTION)) {
        ret = NULL;
        if (RETURN_VALUE_USED(opline)) {
            ret = EX_VAR(opline->result.var);
            ZVAL_NULL(ret);
        }

        call->prev_execute_data = execute_data;
        i_init_func_execute_data(call, &fbc->op_array, ret);

        if (EXPECTED(zend_execute_ex == execute_ex)) {
            ZEND_VM_ENTER();
        } else {
            ZEND_ADD_CALL_FLAG(call, ZEND_CALL_TOP);
            zend_execute_ex(call);
        }
    }

    ...

    ZEND_VM_SET_OPCODE(opline + 1);
    ZEND_VM_CONTINUE();
}

能够见见,DO_FCALL 首先将调用函数前的上下文数据保存到 call->prev_execute_data,然后调用 i_init_func_execute_data 函数,将自定义函数对象中的 op_array(每一种自定义函数会在编写翻译的时候生成对应的数额,其数据构造中蕴藏了函数的
OPCODE 数组) 赋值给新的履行上下文对象。

然后,调用 zend_execute_ex 函数,最先进行自定义的函数。zend_execute_ex 实际上便是前边提到的 execute_ex 函数(默许是那样,但扩张只怕重写 zend_execute_ex 指针,这么些API 让 PHP
扩张开荒者能够经过覆写函数达到扩张功效的目标,不是本篇的核心,不允许备深入斟酌),只是上下文数据被替换来当前函数所在的上下文数据。

咱俩得以那样精通,最外层的代码正是叁个暗中认可存在的函数(肖似 C
语言中的 main()函数),和客商自定义的函数本质上是尚未分别的。

设若大家未来选拔的是CLI形式,直接在SAPI/cli/php_cli.c文件中找到main函数,
暗中认可情形下PHP的CLI形式的作为情势为PHP_MODE_STANDALX570D。
此行为格局中PHP内核会调用php_execute_script(&file_handle
TSRMLS_CC卡塔尔;来施行PHP文件。
顺着那条推行的路径,能够看来一个PHP文件在经过词法剖判,语法剖析,编写翻译后生成人中学间代码的经过:

4.

讲到这里,大家了解opcode怎么和handler对应了。可是在整机上还会有五个进度,正是语法剖析,分析后有着的opcode是怎么串联起来的吧?

语法深入解析的细节就背着了,解析过后,会有个富含全部opcode的大数组(说链表恐怕越来越纯粹),从地方代码大家得以见见,每一个handler施行完后,都会调用
ZEND_VM_NEXT_OPCODE(State of Qatar,抽取下一个opcode,继续实行,直到最终退出,循环的代码
vim Zend/zend_vm_execute.h +337:

ZEND_API void execute_ex(zend_execute_data *execute_data TSRMLS_DC)
{
›   DCL_OPLINE
›   zend_bool original_in_execution;



›   original_in_execution = EG(in_execution);
›   EG(in_execution) = 1;

›   if (0) {
zend_vm_enter:
›   ›   execute_data = i_create_execute_data_from_op_array(EG(active_op_array), 1 TSRMLS_CC);
›   }

›   LOAD_REGS();
›   LOAD_OPLINE();

›   while (1) {
    ›   int ret;
#ifdef ZEND_WIN32
›   ›   if (EG(timed_out)) {
›   ›   ›   zend_timeout(0);
›   ›   }
#endif

›   ›   if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
›   ›   ›   switch (ret) {
›   ›   ›   ›   case 1:
›   ›   ›   ›   ›   EG(in_execution) = original_in_execution;
›   ›   ›   ›   ›   return;
›   ›   ›   ›   case 2:
›   ›   ›   ›   ›   goto zend_vm_enter;
›   ›   ›   ›   ›   break;
›   ›   ›   ›   case 3:
›   ›   ›   ›   ›   execute_data = EG(current_execute_data);
›   ›   ›   ›   ›   break;
›   ›   ›   ›   default:
›   ›   ›   ›   ›   break;
›   ›   ›   }
›   ›   }

›   }
›   zend_error_noreturn(E_ERROR, "Arrived at end of main loop which shouldn't happen");
}

宏定义, vim Zend/zend_execute.c +1772

1772 #define ZEND_VM_NEXT_OPCODE() \
1773 ›   CHECK_SYMBOL_TABLES() \
1774 ›   ZEND_VM_INC_OPCODE(); \
1775 ›   ZEND_VM_CONTINUE()

329 #define ZEND_VM_CONTINUE()         return 0
330 #define ZEND_VM_RETURN()           return 1
331 #define ZEND_VM_ENTER()            return 2
332 #define ZEND_VM_LEAVE()            return 3

while是三个死循环,推行二个handler函数,除个别情状,许多handler函数末尾都调用ZEND_VM_NEXT_OPCODE()
-> ZEND_VM_CONTINUE(卡塔尔国,return 0,继续循环。

注:比方 yield
协程是个例外,它会回来1,直接return出循环。未来有机会大家再独自对yield做深入分析。

瞩望你看完上面内容,对PHP Zend
引擎的解析进度有个详细的询问,下边大家依照原理的剖析,再轻巧聊聊PHP的优化。

opcode是计算机指令中的大器晚成有的,用于内定要施行的操作,
指令的格式和标准由微机的通令标准钦定。
除了指令自己以外经常还应该有指令所须要的操作数,也可能有的指令无需显式的操作数。
那几个操作数恐怕是存放器中的值,货仓中的值,某块内部存款和储蓄器的值也许IO端口中的值等等

逻辑跳转

我们驾驭指令都是逐风华正茂试行的,而笔者辈的顺序,通常都带有众多的逻辑判定和循环,那有的又是何许通过
OPCODE 达成的啊?

<?php
$a = 10;
if ($a == 10) {
    echo 'success';
} else {
    echo 'failure';
}

作者们依旧经过 vld 查看 OPCODE(必须要说 vld 扩张是深入分析 PHP 的神器)。

compiled vars:  !0 = $a
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 10
   3     1        IS_EQUAL                                         ~2      !0, 10
         2      > JMPZ                                                     ~2, ->5
   4     3    >   ECHO                                                     'success'
         4      > JMP                                                      ->6
   6     5    >   ECHO                                                     'failure'
   7     6    > > RETURN                                                   1

笔者们见到,JMPZ 和 JMP 调控了执行流程。JMP 的逻辑很简单,将眼下的
OPCODE 指针指向内需跳转的 OPCODE。

ZEND_VM_HANDLER(42, ZEND_JMP, JMP_ADDR, ANY)
{
    USE_OPLINE

    ZEND_VM_SET_OPCODE(OP_JMP_ADDR(opline, opline->op1));
    ZEND_VM_CONTINUE();
}

JMPZ 仅仅是多了叁次剖断,依据结果选拔是或不是跳转,这里就不再另行列举了。而管理循环的点子与推断基本上是相近的。

<?php
$a = [1, 2, 3];
foreach ($a as $n) {
    echo $n;
}

compiled vars:  !0 = $a, !1 = $n
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, <array>
   3     1      > FE_RESET_R                                       $3      !0, ->5
         2    > > FE_FETCH_R                                               $3, !1, ->5
   4     3    >   ECHO                                                     !1
         4      > JMP                                                      ->2
         5    >   FE_FREE                                                  $3
   5     6      > RETURN                                                   1

巡回只必要 JMP 指令就能够产生,通过 FE_FETCH_R 指令决断是不是业已达到数组的末段,假若达到则脱离循环。

1 EG(active_op_array) = zend_compile_file(file_handle, type TSRMLS_CC);

5. PHP优化注意事项

日常opcode还会有另朝气蓬勃种称谓: 字节码。
比方Java设想机,.NET的通用中间语言(CIL: Common Intermeditate
Language卡塔尔(قطر‎等等

结语

由此领悟 Zend 设想机,相信你对 PHP
是怎么运作的,会有更浓厚的明白。想到大家写的生龙活虎行行代码,最终机器施行的时候会产生成千上万的吩咐,每一种指令又重整旗鼓在复杂的拍卖逻辑之上。那么些过去专擅写下的代码,以后会不会在脑海里不自觉的调换来OPCODE 再品尝黄金年代番啊?

在销毁了文件所在的handler后,假设存在中间代码,则PHP设想机将经过以下代码实践中间代码:

5.1 echo 输出

<?php
    $foo = 'foo';
    $bar = 'bar';
    echo $foo . $bar;

vld 查看opcode:

number of ops:  5
compiled vars:  !0 = $foo, !1 = $bar
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 'foo'
   3     1        ASSIGN                                                   !1, 'bar'
   4     2        CONCAT                                           ~2      !0, !1
         3        ECHO                                                     ~2
   5     4      > RETURN                                                   1

branch: #  0; line:     2-    5; sop:     0; eop:     4; out1:  -2
path #1: 0,

ZEND_CONCAT 连接 $a和$b的值,保存到有时变量~第22中学,然后echo
出来。那一个进程中涉及要分配一块内部存款和储蓄器,用于偶然变量,用完后还要释放,还亟需调用拼接函数,实践拼接进度。

假如换到这么写:

<?php
    $foo = 'foo';
    $bar = 'bar';
    echo $foo, $bar;

对应的opcode:

number of ops:  5
compiled vars:  !0 = $foo, !1 = $bar
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 'foo'
   3     1        ASSIGN                                                   !1, 'bar'
   4     2        ECHO                                                     !0
         3        ECHO                                                     !1
   5     4      > RETURN                                                   1

branch: #  0; line:     2-    5; sop:     0; eop:     4; out1:  -2
path #1: 0,

无需分配内部存款和储蓄器,也不须求实行拼接函数,是否功用更加好吧!想询问拼接进度,能够依赖本文讲的原委,自行检索
ZEND_CONCAT 这么些opcode对应的handler,做了无数事务啊。

PHP中的opcode则归于前面介绍中的后着,PHP是构建在Zend虚构机之上的。PHP的opcode便是Zend设想机中的指令

1 zend_execute(EG(active_op_array) TSRMLS_CC);

5.2 define()和const

const关键字是从5.3先河引进的,和define有超大差异,和C语言的#define倒是含义大概。

  • define(卡塔尔 是函数调用,有函数调用费用。
  • const
    是主要字,直接生成opcode,归属编写翻译期能明确的,没有必要动态在试行期分配。

const 的值是死的,运维时不得以退换,所以说相通C语言的
#define,归于编写翻译时期就鲜明的源委,并且对数值类型有节制。

直白看代码,相比opcode:

define例子:

<?php
    define('FOO', 'foo');
    echo FOO;

define opcode:

number of ops:  6
compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   SEND_VAL                                                 'FOO'
         1        SEND_VAL                                                 'foo'
         2        DO_FCALL                                      2          'define'
   3     3        FETCH_CONSTANT                                   ~1      'FOO'
         4        ECHO                                                     ~1
   4     5      > RETURN                                                   1

const例子:

<?php
    const FOO = 'foo';
    echo FOO;

const opcode:

number of ops:  4
compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   DECLARE_CONST                                            'FOO', 'foo'
   3     1        FETCH_CONSTANT                                   ~0      'FOO'
         2        ECHO                                                     ~0
   4     3      > RETURN                                                   1

Relevant Link:

若果你是选用VS查看源码的话,将光标移到zend_execute并直接按F12,
你会意识zend_execute的概念跳转到了叁个指针函数的注解(Zend/zend_execute_API.c)。

5.3 动态函数的代价

<?php
    function foo() { }
    foo();

对应opcode:

number of ops:  3
compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   NOP
   3     1        DO_FCALL                                      0          'foo'
   4     2      > RETURN                                                   1

动态调用的代码:

<?php
    function foo() { }
    $a = 'foo';
    $a();

opcode:

number of ops:  5
compiled vars:  !0 = $a
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   NOP
   3     1        ASSIGN                                                   !0, 'foo'
   4     2        INIT_FCALL_BY_NAME                                       !0
         3        DO_FCALL_BY_NAME                              0
   5     4      > RETURN                                                   1

可以 vim Zend/zend_vm_def.h
+2630,看看INIT_FCALL_BY_NAME做的政工,代码太长,这里不列出来了。动态天性就算实惠,但一定会就义质量,所以采用前要平衡利弊。

1 ZEND_API void (*zend_execute)(zend_op_array *op_array TSRMLS_DC);

5.4 类的推移评释的代价

抑或先看代码:

<?php
    class Bar { }
    class Foo extends Bar { }

对应opcode:

number of ops:  4
compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   NOP
   3     1        NOP
         2        NOP
   4     3      > RETURN                                                   1

轮流证明顺序:

<?php
    class Foo extends Bar { }
    class Bar { }

对应opcode:

number of ops:  4
compiled vars:  none
line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   FETCH_CLASS                                   0  :0      'Bar'
         1        DECLARE_INHERITED_CLASS                                  '%00foo%2FUsers%2Fqisen%2Ftmp%2Fvld.php0x103d58020', 'foo'
   3     2        NOP
   4     3      > RETURN                                                   1

要是在强语言中,前边的写法会发生编写翻译错误,但PHP这种动态语言,会把类的扬言推迟到运转时,即便您不留意,就很恐怕踩到那一个雷。

进而在我们理解Zend
VM原理后,就更应当小心少用动态本性,秋毫之末的时候,就势必不要用。

转自:

2. PHP中的Opcode

那是一个大局的函数指针,它的功力就是实施PHP代码文件深入分析完的转成的zend_op_array。
和zend_execute相仿的还恐怕有贰个zedn_execute_internal函数,它用来施行内部函数。
在PHP内核运维时(zend_startup卡塔尔时,这么些大局函数指针将会指向execute函数。
注意函数指针前面包车型客车梳洗符ZEND_API,那是ZendAPI的大器晚成部分。
在zend_execute函数指针赋值时,还应该有PHP的中间代码编写翻译函数zend_compile_file(文件情势)和zend_compile_string(字符串方式State of Qatar。

0x1: 数据构造

1 zend_compile_file = compile_file;

在PHP完结内部,opcode由如下的构造体表示

2 zend_compile_string = compile_string;

\php-5.6.17\Zend\zend_compile.h

3 zend_execute = execute;
struct _zend_op {opcode_handler_t handler; // 执行该opcode时调用的处理函数znode_op op1; // opcode所操作的操作数znode_op op2; // opcode所操作的操作数znode_op result;ulong extended_value;uint lineno;zend_uchar opcode; // opcode代码zend_uchar op1_type;zend_uchar op2_type;zend_uchar result_type;}; 
4 zend_execute_internal = NULL;

和CPU的一声令下相像,有一个标识指令的opcode字段,以致那些opcode所操作的操作数,PHP不像汇编那么底层,
在本子实际试行的时候可能还必要其余更加多的消息,extended_澳门新萄京8522,value字段就封存了那类新闻,
此中的result域则是保留该指令实行到位后的结果

5 zend_throw_exception_hook = NULL;

诸如如下代码是在编译器境遇print语句的时候举行编写翻译的函数

那多少个全局的函数指针均只调用了系统暗许完毕的多少个函数,比方compile_file和compile_string函数,
他们都是以大局函数指针存在,这种实现方式在PHP内核中生机勃勃种类,其优势在于更低的耦合度,以致足以定制这个函数。
例如在APC等opcode优化扩张中正是透过更换系统默许的zend_compile_file函数指针为友好的函数指针my_compile_file,
并且在my_compile_file中追加缓存等效果。

\php-5.6.17\Zend\zend_compile.c

到此处我们找到了中间代码推行的末尾函数:execute(Zend/zend_vm_execure.hState of Qatar。
在此个函数中装有的中间代码的实施最后都会调用handler。那个handler是哪些吧?

void zend_do_print(znode *result, const znode *arg TSRMLS_DC) /* {{{ */{ //新创建一条zend_op zend_op *opline = get_next_op TSRMLS_CC);//将新建的zend_op的返回值类型设置为临时变量,因为print中的内存仅仅为了临时输出,并不需要保存opline->result_type = IS_TMP_VAR;//为临时变量申请空间opline->result.var = get_temporary_variable;//指定opcode为ZEND_PRINTopline->opcode = ZEND_PRINT;//将传递进来的参数赋值给这条opcode的第一个操作数SET_NODE;SET_UNUSED;GET_NODE(result, opline->result);}
1 if ((ret = EX(opline)->handler(execute_data TSRMLS_CC)) > 0) {

0x2: opcode类型: zend_op->zend_uchar opcode

2 }

比对汇编语言的概念,每一个opcode都对应于三个品类,评释该opcpde的”操作指令”,opcode的门类为zend_uchar,zend_uchar实际上便是unsigned
char,此字段保存的整形值即为op的编号,用来区分分歧的op类型,opcode的可取值都被定义成了宏

此地的handler是二个函数指针,它指向执行该opcode时调用的管理函数。
那时候大家供给探视handler函数指针是哪些棉被服装置的。
在前边大家有涉及和execute一齐设置的全局指针函数:zend_compile_string。
它的职能是编写翻译字符串为中间代码。在Zend/zend_language_scanner.c文件中有compile_string函数的兑现。
在这里函数中,当深入分析完全中学间代码后,平时情状下,它会实践pass_two(Zend/zend_opcode.c)函数。
pass_two那些函数,从其命名上真有一些看不出其含义是什么。
不过大家关怀的是在函数内部,它遍历整当中间代码集合,
调用ZEND_VM_SET_OPCODE_HANDLEENCORE(opline卡塔尔国;为各在那之中间代码设置管理函数。
ZEND_VM_SET_OPCODE_HANDLER是zend_vm_set_opcode_handler函数的接口宏,
zend_vm_set_opcode_handler函数定义在Zend/zend_vm_execute.h文件。
其代码如下:

/Zend/zend_vm_opcodes.h

01 static opcode_handler_t zend_vm_get_opcode_handler(zend_uchar opcode, zend_op* op)
#define ZEND_NOP 0#define ZEND_ADD 1#define ZEND_SUB 2#define ZEND_MUL 3#define ZEND_DIV 4#define ZEND_MOD 5#define ZEND_SL 6#define ZEND_SR 7#define ZEND_CONCAT 8#define ZEND_BW_OR 9#define ZEND_BW_AND 10#define ZEND_BW_XOR 11#define ZEND_BW_NOT 12#define ZEND_BOOL_NOT 13#define ZEND_BOOL_XOR 14#define ZEND_IS_IDENTICAL 15#define ZEND_IS_NOT_IDENTICAL 16#define ZEND_IS_EQUAL 17#define ZEND_IS_NOT_EQUAL 18#define ZEND_IS_SMALLER 19#define ZEND_IS_SMALLER_OR_EQUAL 20#define ZEND_CAST 21#define ZEND_QM_ASSIGN 22#define ZEND_ASSIGN_ADD 23#define ZEND_ASSIGN_SUB 24#define ZEND_ASSIGN_MUL 25#define ZEND_ASSIGN_DIV 26#define ZEND_ASSIGN_MOD 27#define ZEND_ASSIGN_SL 28#define ZEND_ASSIGN_SR 29#define ZEND_ASSIGN_CONCAT 30#define ZEND_ASSIGN_BW_OR 31#define ZEND_ASSIGN_BW_AND 32#define ZEND_ASSIGN_BW_XOR 33#define ZEND_PRE_INC 34#define ZEND_PRE_DEC 35#define ZEND_POST_INC 36#define ZEND_POST_DEC 37#define ZEND_ASSIGN 38#define ZEND_ASSIGN_REF 39#define ZEND_ECHO 40#define ZEND_PRINT 41#define ZEND_JMP 42#define ZEND_JMPZ 43#define ZEND_JMPNZ 44#define ZEND_JMPZNZ 45#define ZEND_JMPZ_EX 46#define ZEND_JMPNZ_EX 47#define ZEND_CASE 48#define ZEND_SWITCH_FREE 49#define ZEND_BRK 50#define ZEND_CONT 51#define ZEND_BOOL 52#define ZEND_INIT_STRING 53#define ZEND_ADD_CHAR 54#define ZEND_ADD_STRING 55#define ZEND_ADD_VAR 56#define ZEND_BEGIN_SILENCE 57#define ZEND_END_SILENCE 58#define ZEND_INIT_FCALL_BY_NAME 59#define ZEND_DO_FCALL 60#define ZEND_DO_FCALL_BY_NAME 61#define ZEND_RETURN 62#define ZEND_RECV 63#define ZEND_RECV_INIT 64#define ZEND_SEND_VAL 65#define ZEND_SEND_VAR 66#define ZEND_SEND_REF 67#define ZEND_NEW 68#define ZEND_INIT_NS_FCALL_BY_NAME 69#define ZEND_FREE 70#define ZEND_INIT_ARRAY 71#define ZEND_ADD_ARRAY_ELEMENT 72#define ZEND_INCLUDE_OR_EVAL 73#define ZEND_UNSET_VAR 74#define ZEND_UNSET_DIM 75#define ZEND_UNSET_OBJ 76#define ZEND_FE_RESET 77#define ZEND_FE_FETCH 78#define ZEND_EXIT 79#define ZEND_FETCH_R 80#define ZEND_FETCH_DIM_R 81#define ZEND_FETCH_OBJ_R 82#define ZEND_FETCH_W 83#define ZEND_FETCH_DIM_W 84#define ZEND_FETCH_OBJ_W 85#define ZEND_FETCH_RW 86#define ZEND_FETCH_DIM_RW 87#define ZEND_FETCH_OBJ_RW 88#define ZEND_FETCH_IS 89#define ZEND_FETCH_DIM_IS 90#define ZEND_FETCH_OBJ_IS 91#define ZEND_FETCH_FUNC_ARG 92#define ZEND_FETCH_DIM_FUNC_ARG 93#define ZEND_FETCH_OBJ_FUNC_ARG 94#define ZEND_FETCH_UNSET 95#define ZEND_FETCH_DIM_UNSET 96#define ZEND_FETCH_OBJ_UNSET 97#define ZEND_FETCH_DIM_TMP_VAR 98#define ZEND_FETCH_CONSTANT 99#define ZEND_GOTO 100#define ZEND_EXT_STMT 101#define ZEND_EXT_FCALL_BEGIN 102#define ZEND_EXT_FCALL_END 103#define ZEND_EXT_NOP 104#define ZEND_TICKS 105#define ZEND_SEND_VAR_NO_REF 106#define ZEND_CATCH 107#define ZEND_THROW 108#define ZEND_FETCH_CLASS 109#define ZEND_CLONE 110#define ZEND_RETURN_BY_REF 111#define ZEND_INIT_METHOD_CALL 112#define ZEND_INIT_STATIC_METHOD_CALL 113#define ZEND_ISSET_ISEMPTY_VAR 114#define ZEND_ISSET_ISEMPTY_DIM_OBJ 115#define ZEND_PRE_INC_OBJ 132#define ZEND_PRE_DEC_OBJ 133#define ZEND_POST_INC_OBJ 134#define ZEND_POST_DEC_OBJ 135#define ZEND_ASSIGN_OBJ 136#define ZEND_INSTANCEOF 138#define ZEND_DECLARE_CLASS 139#define ZEND_DECLARE_INHERITED_CLASS 140#define ZEND_DECLARE_FUNCTION 141#define ZEND_RAISE_ABSTRACT_ERROR 142#define ZEND_DECLARE_CONST 143#define ZEND_ADD_INTERFACE 144#define ZEND_DECLARE_INHERITED_CLASS_DELAYED 145#define ZEND_VERIFY_ABSTRACT_CLASS 146#define ZEND_ASSIGN_DIM 147#define ZEND_ISSET_ISEMPTY_PROP_OBJ 148#define ZEND_HANDLE_EXCEPTION 149#define ZEND_USER_OPCODE 150#define ZEND_JMP_SET 152#define ZEND_DECLARE_LAMBDA_FUNCTION 153#define ZEND_ADD_TRAIT 154#define ZEND_BIND_TRAITS 155#define ZEND_SEPARATE 156#define ZEND_QM_ASSIGN_VAR 157#define ZEND_JMP_SET_VAR 158#define ZEND_DISCARD_EXCEPTION 159#define ZEND_YIELD 160#define ZEND_GENERATOR_RETURN 161#define ZEND_FAST_CALL 162#define ZEND_FAST_RET 163#define ZEND_RECV_VARIADIC 164#define ZEND_SEND_UNPACK 165#define ZEND_POW 166#define ZEND_ASSIGN_POW 167 
02 {

0x3: opcode施行句柄: zend_op->handler

03         static const int zend_vm_decode[] = {

op的实行句柄,其品种为opcode_handler_t

04             _UNUSED_CODE, /* 0              */

typedef int (ZEND_FASTCALL *opcode_handler_t)
(ZEND_OPCODE_HANDLER_ARGS);

05             _CONST_CODE,  /* 1 = IS_CONST   */

其少年老成函数指针为op定义了进行办法,每意气风发种opcode字段都对应一个类型的handler,比方如若$a

1;那样的代码生成的op,操作数为const和cv,最终就会分明handler为函数ZEND_ASSIGN_SPEC_CV_CONST_HANDLER

/Zend/zend_vm_execute.h

void zend_init_opcodes_handlers{static const opcode_handler_t labels[] = {..ZEND_ASSIGN_SPEC_CV_CONST_HANDLER,..}}

0x4: opcpde操作数znode

操作数字段是_zend_op类型中比较关键的片段了,当中op1,op2,result八个操作数定义为znode类型

\php-5.6.17\Zend\zend_compile.h

typedef struct _znode { /* used only during compilation *//*这个int类型的字段定义znode操作数的类型#define IS_CONST  //表示常量,例如$a = 123; $b = "hello";这些代码生成OP后,123和"hello"都是以常量类型操作数存在#define IS_TMP_VAR  //表示临时变量,临时变量一般在前面加~来表示,这是一些OP执行过程中需要用到的中间变量,例如初始化一个数组的时候,就需要一个临时变量来暂时存储数组zval,然后将数组赋值给变量#define IS_VAR  //一般意义上的变量,以$开发表示#define IS_UNUSED  // Unused variable #define IS_CV  // Compiled variable,这种类型的操作数比较重要,此类型是在PHP后来的版本中中才出现,CV的意思是compiled variable,即编译后的变量,变量都是保存在一个符号表中,这个符号表是一个哈希表,如果每次读写变量的时候都需要到哈希表中去检索,会对效率有一定的影响,因此在执行上下文环境中,会将一些编译期间生成的变量缓存起来。此类型操作数一般以!开头表示,比如变量$a=123;$b="hello"这段代码,$a和$b对应的操作数可能就是!0和!1, 0和1相当于一个索引号,通过索引号从缓存中取得相应的值*/int op_type;/*此字段为一个联合体,根据op_type的不同,u取不同的值1. op_type=IS_CONST的时候,u中的constant保存的就是操作数对应的zval结构2. 例如$a=123时,123这个操作数中,u中的constant是一个IS_LONG类型的zval,其值lval为123 */union {znode_op op;zval constant; /* replaced by literal/zv */zend_op_array *op_array;zend_ast *ast;} u;zend_uint EA; /* extended attributes */} znode; 

0x5: opcode编写翻译后数组op_array

在zend_do_print函数中的第意气风发行,大家注意到下边那行代码

zend_op *opline = get_next_op TSRMLS_CC); 

PHP脚本代码被编写翻译后发生的opcode保存在op_array中,其里面存款和储蓄的布局如下

\php-5.6.17\Zend\zend_compile.h

struct _zend_op_array {/* Common elements */zend_uchar type;const char *function_name; // 如果是用户定义的函数则,这里将保存函数的名字zend_class_entry *scope;zend_uint fn_flags;union _zend_function *prototype;zend_uint num_args;zend_uint required_num_args;zend_arg_info *arg_info;/* END of common elements */zend_uint *refcount;zend_op *opcodes; // opcode数组zend_uint last;zend_compiled_variable *vars;int last_var;zend_uint T;zend_uint nested_calls;zend_uint used_stack;zend_brk_cont_element *brk_cont_array;int last_brk_cont;zend_try_catch_element *try_catch_array;int last_try_catch;zend_bool has_finally_block;/* static variables support */HashTable *static_variables;zend_uint this_var;const char *filename;zend_uint line_start;zend_uint line_end;const char *doc_comment;zend_uint doc_comment_len;zend_uint early_binding; /* the linked list of delayed declarations */zend_literal *literals;int last_literal;void **run_time_cache;int last_cache_slot;void *reserved[ZEND_MAX_RESERVED_RESOURCES];}; 

一切PHP脚本代码被编写翻译后的opcodes保存在此,在奉行的时候由上面包车型大巴execute函数试行

ZEND_API void execute(zend_op_array *op_array TSRMLS_DC){// ... 循环执行op_array中的opcode或者执行其他op_array中的opcode}

每条opcode都有贰个opcode_handler_t的函数指针字段,用于执行该opcode,PHP有三种格局来开展opcode的管理

  1. CALL: PHP默许使用CALL的措施,也正是函数调用的艺术2. SWITCH:
    由于opcode施行是各类PHP程序往往需求开展的操作,能够选取SWITCH或然GOTO的格局来散发3.
    GOTO: 平日GOTO的频率相对会高级中学一年级些,但是效能是不是提升信任于区别的CPU
    实际上大家会意识,在/zend/zend_language_parser.c中正是Zend的opcode翻译解释推行进度,个中饱含了call、switch、goto三种opcode实践格局

这就是PHP为啥称之为解释型语言的根本原理,PHP在做到Lex词法深入解析后,在语法解析即生成发生式的时候,直接通过call、switch、goto的主意调用zend
api实行固然解释实践

Relevant Link:

http://www.nowamagic.net/librarys/veda/detail/1325http://php.net/manual/zh/internals2.opcodes.list.phphttp://www.nowamagic.net/librarys/veda/detail/1543http://www.nowamagic.net/librarys/veda/detail/1324http://www.nowamagic.net/librarys/veda/detail/1543 http://www.laruence.com/2008/06/18/221.htmlhttp://www.php-internals.com/book/?p=chapt02/02-03-02-opcode 

3. opcode翻译实践

Relevant Link:

http://www.php-internals.com/book/?p=chapt02/02-03-03-from-opcode-to-handler

上述所述本文给大家介绍的PHP内核学习课程之php
opcode内查验现的连带知识,希望对大家有着帮衬。

06             _TMP_CODE,    /* 2 = IS_TMP_VAR */
07             _UNUSED_CODE, /* 3              */

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图