1. Zend引擎主要包含兩個核心部分:編譯、執行:
執行階段主要用到的數據結構:
opcode: php代碼編譯產生的zend虛擬機可識別的指令,php7有173個opcode,定義在 zend_vm_opcodes.hPHP中的所有語法實現都是由這些opcode組成的。
struct _zend_op { const void *handler; //對應執行的C語言function,即每條opcode都有一個C function處理 znode_op op1; //操作數1 znode_op op2; //操作數2 znode_op result; //返回值 uint32_t extended_value; uint32_t lineno; zend_uchar opcode; //opcode指令 zend_uchar op1_type; //操作數1類型 zend_uchar op2_type; //操作數2類型 zend_uchar result_type; //返回值類型 };
zend_op_array : zend引擎執行階段的輸入數據結構,整個執行階段都是操作這個數據結構。
zend_op_array有三個核心部分:opcode指令(對應c的指令)
字面量存儲(變量初始值、調用的函數名稱、類名稱、常量名稱等等稱之為字面量)
變量分配的情況 (當前array定義的變量 臨時變量的數量 編號,執行初始化一次性分配zval,使用時完全按照標號索引不是根據變量名)
zend_executor_globals PHP整個生命周期中最主要的一個結構,是一個全局變量,在main執行前分配(非ZTS下),直到PHP退出,它記錄着當前請求全部的信息,經常見到的一個宏EG
操作的就是這個結構。
定義在zend_globals.h
中:
zend_execute_data 是執行過程中最核心的一個結構,每次函數的調用、include/require、eval等都會生成一個新的結構,它表示當前的作用域、代碼的執行位置以及局部變量的分配等等,等同於機器碼執行過程中stack的角色,后面分析具體執行流程的時候會詳細分析其作用。
zend_execute_data與zend_op_array的關聯關系:
2.執行過程
Zend的executor與linux二進制程序執行的過程是非常類似的。
在C程序執行時有兩個寄存器ebp、esp分別指向當前作用棧的棧頂、棧底,局部變量全部分配在當前棧,函數調用、返回通過call
、ret
指令完成,調用時call
將當前執行位置壓入棧中,返回時ret
將之前執行位置出棧,跳回舊的位置繼續執行。
Zend VM中zend_execute_data
就扮演了這兩個角色,zend_execute_data.prev_execute_data
保存的是調用方的信息,實現了call/ret
,zend_execute_data
后面會分配額外的內存空間用於局部變量的存儲,實現了ebp/esp
的作用。
a. 為當前作用域分配一塊內存,充當運行棧,zend_execute_data結構、所有局部變量、中間變量等等都在此內存上分配
b.初始化全局變量符號表,然后將全局執行位置指針EG(current_execute_data)指向步驟a新分配的zend_execute_data,然后將zend_execute_data.opline指向op_array的起始位置
c.從EX(opline)開始調用各opcode的C處理handler(即_zend_op.handler),每執行完一條opcode將EX(opline)++
繼續執行下一條,直到執行完全部opcode
if語句將根據條件的成立與否決定EX(opline) + offset
所加的偏移量,實現跳轉
如果是函數調用,則首先從EG(function_table)中根據function_name取出此function對應的編譯完成的zend_op_array,然后像步驟a一樣新分配一個zend_execute_data結構,將EG(current_execute_data)賦值給新結構的prev_execute_data
,再將EG(current_execute_data)指向新的zend_execute_data,最后從新的zend_execute_data.opline
開始執行,切換到函數內部,函數執行完以后將EG(current_execute_data)重新指向EX(prev_execute_data),釋放分配的運行棧,銷毀局部變量,繼續從原來函數調用的位置執行
類方法的調用與函數基本相同
d.全部opcode執行完成后將步驟a分配的內存釋放,這個過程會將所有的局部變量"銷毀",執行階段結束
首先根據zend_execute_data
、當前zend_op_array
中局部/臨時變量數計算需要的內存空間,編譯階段zend_op_array的結果,在編譯過程中已經確定當前作用域下有多少個局部變量(func->op_array.last_var)、臨時/中間/無用變量(func->op_array.T),從而在執行之初就將他們全部分配完成。