PHP核心探索:zend_execute的具體執行過程

來源:互聯網
上載者:User
PHP核心探索:zend_execute的具體執行過程

解譯器引擎最終執行op的函數是zend_execute,實際上zend_execute是一個函數指標,在引擎初始化的時候zend_execute預設指向了execute,這個execute定義在{PHPSRC}/Zend/zend_vm_execute.h:

ZEND_API void execute(zend_op_array *op_array TSRMLS_DC)  {      zend_execute_data *execute_data;      zend_bool nested = 0;      zend_bool original_in_execution = EG(in_execution);          if (EG(exception)) {          return;      }        EG(in_execution) = 1;    zend_vm_enter:      /* Initialize execute_data */      execute_data = (zend_execute_data *)zend_vm_stack_alloc(          ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)) +          ZEND_MM_ALIGNED_SIZE(sizeof(zval**) * op_array->last_var * (EG(active_symbol_table) ? 1 : 2)) +          ZEND_MM_ALIGNED_SIZE(sizeof(temp_variable)) * op_array->T TSRMLS_CC);        EX(CVs) = (zval***)((char*)execute_data + ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)));      memset(EX(CVs), 0, sizeof(zval**) * op_array->last_var);      EX(Ts) = (temp_variable *)(((char*)EX(CVs)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval**) * op_array->last_var * (EG(active_symbol_table) ? 1 : 2)));      EX(fbc) = NULL;      EX(called_scope) = NULL;      EX(object) = NULL;      EX(old_error_reporting) = NULL;      EX(op_array) = op_array;      EX(symbol_table) = EG(active_symbol_table);      EX(prev_execute_data) = EG(current_execute_data);      EG(current_execute_data) = execute_data;      EX(nested) = nested;      nested = 1;        if (op_array->start_op) {          ZEND_VM_SET_OPCODE(op_array->start_op);      } else {          ZEND_VM_SET_OPCODE(op_array->opcodes);      }        if (op_array->this_var != -1 && EG(This)) {          Z_ADDREF_P(EG(This)); /* For $this pointer */          if (!EG(active_symbol_table)) {              EX(CVs)[op_array->this_var] = (zval**)EX(CVs) + (op_array->last_var + op_array->this_var);              *EX(CVs)[op_array->this_var] = EG(This);          } else {              if (zend_hash_add(EG(active_symbol_table), "this", sizeof("this"), &EG(This), sizeof(zval *), (void**)&EX(CVs)[op_array->this_var])==FAILURE) {                  Z_DELREF_P(EG(This));              }          }      }        EG(opline_ptr) = &EX(opline);        EX(function_state).function = (zend_function *) op_array;      EX(function_state).arguments = NULL;            while (1) {          int ret;  #ifdef ZEND_WIN32          if (EG(timed_out)) {              zend_timeout(0);          }  #endif            if ((ret = EX(opline)->handler(execute_data TSRMLS_CC)) > 0) {              switch (ret) {                  case 1:                      EG(in_execution) = original_in_execution;                      return;                  case 2:                      op_array = EG(active_op_array);                      goto zend_vm_enter;                  case 3:                      execute_data = EG(current_execute_data);                  default:                      break;              }          }        }      zend_error_noreturn(E_ERROR, "Arrived at end of main loop which shouldn't happen");  }



此函數的參數為op_array,這是一個指向zend_op_array的指標,op_array是在編譯過程中產生,這裡有必要介紹一下zend_op_array這個類型。

zend_op_array簡介

此類型的定義在{PHPSRC}/Zend/zend_compile.h:

struct _zend_op_array {      /* Common elements */      zend_uchar type;      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;      zend_bool pass_rest_by_reference;      unsigned char return_reference;      /* END of common elements */        zend_bool done_pass_two;        zend_uint *refcount;        zend_op *opcodes;      zend_uint last, size;        zend_compiled_variable *vars;      int last_var, size_var;        zend_uint T;        zend_brk_cont_element *brk_cont_array;      int last_brk_cont;      int current_brk_cont;        zend_try_catch_element *try_catch_array;      int last_try_catch;        /* static variables support */      HashTable *static_variables;        zend_op *start_op;      int backpatch_count;        zend_uint this_var;        char *filename;      zend_uint line_start;      zend_uint line_end;      char *doc_comment;      zend_uint doc_comment_len;      zend_uint early_binding; /* the linked list of delayed declarations */        void *reserved[ZEND_MAX_RESERVED_RESOURCES];  };    typedef struct _zend_op_array zend_op_array;



此結構比較複雜,我們目前只介紹最基本的幾個欄位。

1.type:

op_array的類型,首先需要說明的是,一段PHP代碼被編譯之後,雖然返回的是一個zend_op_array指標,但是實際上產生的zend_op_array結構可能不止一個,通過這個結構中的一些欄位,例如function_name ,num_args等你也許會發現這個zend_op_array結構似乎能和函數產生一定的聯絡,確實如此,使用者自訂的函數,以及使用者定義的類的方法,都是一個zend_op_array結構,這些zend_op_array結構在編譯過程中被儲存在某些地方,例如使用者自訂的函數被儲存進了GLOBAL_FUNCTION_TABLE,這個是全域函數符號表,通過函數名可以在此表中檢索到函數體。那麼編譯後返回的那個zend_op_array指標是什麼呢,其實編譯後返回的zend_op_array是執行的一個入口,也可以認為它是最外層,即不在任何函數體內的全域程式碼群組成的op_array。然而全域代碼,使用者自訂函數,使用者自訂的方法都擁有相同的type值:2 ,type可取值的宏定義為:

#define ZEND_INTERNAL_FUNCTION              1  #define ZEND_USER_FUNCTION                  2  #define ZEND_OVERLOADED_FUNCTION            3  #define ZEND_EVAL_CODE                      4  #define ZEND_OVERLOADED_FUNCTION_TEMPORARY  5



可以看到全域代碼,使用者函數,使用者方法都對應的是ZEND_USER_FUNCTION,這個也是最常見的type了,其中ZEND_EVAL_CODE對應的是eval函數中的PHP代碼,所以我們可以想到,eval函數參數中的PHP代碼也會被編譯成單獨的zend_op_array。

2.function_name

如果op_array是由使用者定義的函數或則方法編譯而產生,那麼此欄位對應函數的名字,如果是全域代碼或則是eval部分的代碼,那麼此欄位為控制。

3.opcodes

這個欄位類型為zend_op *,因此這是一個zend_op的數組,這個數組儲存的就是此編譯過程中產生的op,如果不瞭解zend_op,可以看看之前的文章 OPcode簡介 , 這個欄位是最重要的部分了,zend_execute最終就是執行這裡儲存的op。

現在基本對參數op_array有了一定的瞭解,那麼我們就開始進入到execute中。

執行過程詳解

execute函數開始的時候是一些基礎變數的申明,其中zend_execute_data *execute_data;是執行期的資料結構,此變數在進行一定的初始化之後將會被傳遞給每個op的handler函數作為參數,op在執行過程中隨時有可能改變execute_data中的內容。

第14行zend_vm_enter 這個跳轉標籤是作為虛擬機器執行的入口,當op中涉及到函數調用的時候,就有可能會跳轉到這裡來執行函數體。

第16行到第19行為execute_data分配空間

第21行到第32行主要是對execute_data進行一些初始化,以及儲存現場工作,要儲存現場是因為在進入函數調用的時候,需要儲存當前一些運行期間的資料,在函數調用結束之後再進行還原,可以想象為作業系統中進程調度,當進程在調出的時候需要儲存寄存器等上下文環境,而當進程被調入的時候再取出來繼續執行。

第41行到第51行主要是在當前動態符號表中加入$this變數,這個是在調用對象的方法時才有必要進行。

第58行開始的while無限迴圈就是開始執行op_array中的opcodes了,在第66行中調用當前執行的op的handler:

EX(opline)->handler(execute_data TSRMLS_CC))



然後如果handler的傳回值小於0則迴圈繼續,如果大於0則進入一個switch結構:

當傳回值為1時:execute函數將返回,執行也就結束了。

當傳回值為2時:op_array被重新設定,並跳轉到zend_vm_enter ,這個一般是函數調用或則執行eval函數中的代碼,將在新的上下文執行相關函數的op_array

當傳回值為3時:迴圈體繼續繼續執行,當然再繼續執行之前,EX(opline)已經往後移了一位(可能多位),也就是已經指向了後面一個新的opline,於是繼續執行新的opline

當返回其他值時:結束迴圈,報錯,結束應該用return,也就是返回1

在op的handler中返回特定的值都被定義成了宏,例如{PHPSRC}/Zend/zend_execute.c中定義的:

#define ZEND_VM_NEXT_OPCODE() /      CHECK_SYMBOL_TABLES() /      EX(opline)++; /      ZEND_VM_CONTINUE()    #define ZEND_VM_SET_OPCODE(new_op) /      CHECK_SYMBOL_TABLES() /      EX(opline) = new_op    #define ZEND_VM_JMP(new_op) /      CHECK_SYMBOL_TABLES() /      if (EXPECTED(!EG(exception))) { /          EX(opline) = new_op; /      } /      ZEND_VM_CONTINUE()    #define ZEND_VM_INC_OPCODE() /      EX(opline)++



以及在{PHPSRC}/Zend/zend_vm_execute.c中定義的:

#define ZEND_VM_CONTINUE()   return 0  #define ZEND_VM_RETURN()     return 1  #define ZEND_VM_ENTER()      return 2  #define ZEND_VM_LEAVE()      return 3  #define ZEND_VM_DISPATCH(opcode, opline) return zend_vm_get_opcode_handler(opcode, opline)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);



簡單介紹功能

ZEND_VM_NEXT_OPCODE():移動到下一條op,返回0,不進入switch,迴圈繼續(這個是最常用到的)

ZEND_VM_SET_OPCODE(new_op):當前opline設定成new_op

ZEND_VM_JMP(new_op) :當前opline設定成new_op,返回0,不進入switch,迴圈繼續

ZEND_VM_INC_OPCODE():僅僅移動到下一條op

執行環境的切換

在前面的內容已經提到,使用者自訂函數,類方法,eval的代碼都會編譯成單獨的op_array,那麼當進行函數調用等操作時,必然涉及到調用前的op_array執行環境和新的函數的op_array執行環境的切換,這一段我們將以調用使用者自訂函數來介紹整個切換過程如何進行。

介紹此過程前必須瞭解執行環境的相關資料結構,涉及到執行環境的資料結構主要有兩個:

1. 執行期全域變數結構

相關的定義在{PHPSRC}/Zend/zend_globals_macros.h:

/* Executor */  #ifdef ZTS  # define EG(v) TSRMG(executor_globals_id, zend_executor_globals *, v)  #else  # define EG(v) (executor_globals.v)  extern ZEND_API zend_executor_globals executor_globals;  #endif



這裡是一個條件編譯,ZTS表示安全執行緒啟用,為了簡化,我們這裡以非安全執行緒模式的情況下來介紹,那麼執行期的全域變數就是executor_globals,其類型為zend_executor_globals, zend_executor_globals的定義在{PHPSRC}/Zend/zend_globals.h,結構比較龐大,這裡包含了整個執行期需要用到的各種變數,無論是哪個op_array在執行,都共用這一個全域變數,在執行過程中,此結構中的一些成員可能會改變,比如當前執行的op_array欄位active_op_array,動態符號表欄位active_symbol_table可能會根據不同的op_array而改變,This指標會根據在不同的對象環境而改變。

另外還定義了一個EG宏來取此變數中的欄位值,此宏是針對安全執行緒和非安全執行緒模式的一個封裝。

2.每個op_array自身的執行資料

針對每一個op_array,都會有自己執行期的一些資料,在函數execute開始的時候我們能看到zend_vm_enter跳轉標籤下面就會初始一個局部變數execute_data,所以我們每次切換到新的op_array的時候,都會為新的op_array建立一個execute_data變數,此變數的類型為zend_execute_data的指標,相關定義在{PHPSRC}/Zend/zend_compile.h:

struct _zend_execute_data {      struct _zend_op *opline;      zend_function_state function_state;      zend_function *fbc; /* Function Being Called */      zend_class_entry *called_scope;      zend_op_array *op_array;      zval *object;      union _temp_variable *Ts;      zval ***CVs;      HashTable *symbol_table;      struct _zend_execute_data *prev_execute_data;      zval *old_error_reporting;      zend_bool nested;      zval **original_return_value;      zend_class_entry *current_scope;      zend_class_entry *current_called_scope;      zval *current_this;      zval *current_object;      struct _zend_op *call_opline;  };



可以用EX宏來取其中的值:#define EX(element) execute_data->element

這裡只簡單介紹其中兩個欄位:

opline: 當前正在執行的op。

prev_execute_data: op_array環境切換的時候,這個欄位用來儲存切換前的op_array,此欄位非常重要,他能將每個op_array的execute_data按照調用的先後順序串連成一個單鏈表,每當一個op_array執行結束要還原到調用前op_array的時候,就通過當前的execute_data中的prev_execute_data欄位來得到調用前的執行器資料。

在executor_globals中的欄位current_execute_data就是指向當前正在執行的op_array的execute_data。

再正式介紹之前還需要簡單的介紹一下使用者自訂函數的調用過程,詳細的過程以後再函數章節中專門介紹,這裡簡單的說明一下:

在調用函數的時候,比如test()函數,會先在全域函數符號表中根據test來搜尋相關的函數體,如果搜尋不到則會報錯函數沒有定義,找到test的函數體之後,取得test函數的op_array,然後跳轉到execute中的goto標籤:zend_vm_enter,於是就進入到了test函數的執行環境。

下面我們將以一段簡單的代碼來介紹執行環境切換過程,例子代碼:

 



這段代碼非常簡單,這樣方便我們介紹原理,複雜的代碼讀者可以舉一反三。此代碼編譯之後會產生兩個op_array,一個是全域代碼的op_array,另外一個是test函數的op_array,其中全域代碼中會通過函數調用進入到test函數的執行環境,執行結束之後,會返回到全域代碼,然後代碼結束。

下面我們分幾個階段來介紹這段代碼的過程,然後從中可以知道執行環境切換的方法。

1. 進入execute函數,開始執行op_array ,這個op_array就是全域代碼的op_array,我們暫時稱其為op_array1

首先在execute中為op_array1建立了一個execute_data資料,我們暫時命名為execute_data1,然後進行相關的初始化操作,其中比較重要的是:

EX(op_array) = op_array; // 設定op_array欄位為當前執行的op_array,也就是全域代碼的op_array1EX(prev_execute_data) = EG(current_execute_data);//將全域執行資料中儲存的當前op_array執行資料儲存到op_array1的execute_data1的prev_execute_data欄位,由於這是執行的第一個op_array,所以prev_execute_data實際上是空值,然後將執行期全域變數的current_execute_data設定成execute_data1,然後設定execute_data1的當前執行op,這樣就可以開始執行當前的op了



2. 在op_array1執行到test函數調用的的時候,首先從全域函數符號表中找到test的函數體,將函數體儲存在execute_data1的function_state欄位,然後從函數體中取到test的op_array,我們這裡用op_array2來表示,並將op_array2賦值給EG(active_op_array):

EG(active_op_array) = &EX(function_state).function->op_array;



於是執行期全域變數的動態op_array欄位指向了函數test的op_array,然後用調用ZEND_VM_ENTER();這個時候會先回到execute函數中的switch結構,並且滿足以下case

case 2:     op_array = EG(active_op_array);     goto zend_vm_enter;



EG(active_op_array)之前已經被我們設定為test函數的op_array2,於是在函數execute中,op_array變數就指向了test的op_array2,然後跳轉到zend_vm_enter。

3. 跳轉到zend_vm_enter之後其實又回到了類似1中的步驟,此時為test的op_array2建立了它的執行資料execute_data,我們這裡用execute_data2來表示。跟1中有些不同的是EX(prev_execute_data) = EG(current_execute_data);這個時候current_execute_data = execute_data1,也就是全域代碼的執行執行期資料,然後EG(current_execute_data) = execute_data;這樣current_execute_data就等於test的執行期資料execute_data2了,同時全域代碼的execute_data1被儲存在execute_data2的prev_execute_data欄位。這個時候進行環境的切換已經完成,於是開始執行test函數。

4. test函數執行完之後就要返回到調用前的執行環境了,也就是全域代碼執行環境,此階段最重要的一個操作就是EG(current_execute_data) = EX(prev_execute_data); 在3中EX(prev_execute_data)已經設定成了全域代碼的execute_data1,所以這樣當前執行資料就變成了全域代碼的執行資料,這樣就成功的從函數test執行環境返回到了全域代碼執行環境

這樣,執行環境的切換過程就完成了,對於深層次的函數調用,原理一樣,執行資料execute_data組成的單鏈表會更長。

延伸閱讀

此文章所在專題列表如下:

PHP核心探索:從SAPI介面開始
PHP核心探索:一次請求的開始與結束
PHP核心探索:一次請求生命週期
PHP核心探索:單進程SAPI生命週期
PHP核心探索:多進程/線程的SAPI生命週期
PHP核心探索:Zend引擎
PHP核心探索:再次探討SAPI
PHP核心探索:Apache模組介紹
PHP核心探索:通過mod_php5支援PHP
PHP核心探索:Apache運行與鉤子函數
PHP核心探索:嵌入式PHP
PHP核心探索:PHP的FastCGI
PHP核心探索:如何執行PHP指令碼
PHP核心探索:PHP指令碼的執行細節
PHP核心探索:作業碼OpCode
PHP核心探索:PHP裡的opcode
PHP核心探索:解譯器的執行過程
PHP核心探索:變數概述
PHP核心探索:變數儲存與類型
PHP核心探索:PHP中的雜湊表
PHP核心探索:理解Zend裡的雜湊表
PHP核心探索:PHP雜湊演算法設計
PHP核心探索:翻譯一篇HashTables文章
PHP核心探索:雜湊碰撞攻擊是什嗎?
PHP核心探索:常量的實現
PHP核心探索:變數的儲存
PHP核心探索:變數的類型
PHP核心探索:變數的值操作
PHP核心探索:變數的建立
PHP核心探索:預定義變數
PHP核心探索:變數的檢索
PHP核心探索:變數的類型轉換
PHP核心探索:弱類型變數的實現
PHP核心探索:靜態變數的實現
PHP核心探索:變數類型提示
PHP核心探索:變數的生命週期
PHP核心探索:變數賦值與銷毀
PHP核心探索:變數範圍
PHP核心探索:詭異的變數名
PHP核心探索:變數的value和type儲存
PHP核心探索:全域變數Global
PHP核心探索:變數類型的轉換
PHP核心探索:記憶體管理開篇
PHP核心探索:Zend記憶體管理器
PHP核心探索:PHP的記憶體管理
PHP核心探索:記憶體的申請與銷毀
PHP核心探索:引用計數與寫時複製
PHP核心探索:PHP5.3的記憶體回收機制
PHP核心探索:記憶體管理中的cache
PHP核心探索:寫時複製COW機制
PHP核心探索:數組與鏈表
PHP核心探索:使用雜湊表API
PHP核心探索:數組操作
PHP核心探索:數組源碼分析
PHP核心探索:函數的分類
PHP核心探索:函數的內部結構
PHP核心探索:函數結構轉換
PHP核心探索:定義函數的過程
PHP核心探索:函數的參數
PHP核心探索:zend_parse_parameters函數
PHP核心探索:函數傳回值
PHP核心探索:形參return value
PHP核心探索:函數調用與執行
PHP核心探索:引用與函數執行
PHP核心探索:匿名函數及閉包
PHP核心探索:物件導向開篇
PHP核心探索:類的結構和實現
PHP核心探索:類的成員變數
PHP核心探索:類的成員方法
PHP核心探索:類的原型zend_class_entry
PHP核心探索:類的定義
PHP核心探索:存取控制
PHP核心探索:繼承,多態與抽象類別
PHP核心探索:魔術函數與延遲綁定
PHP核心探索:保留類與特殊類
PHP核心探索:對象
PHP核心探索:建立對象執行個體
PHP核心探索:對象屬性讀寫
PHP核心探索:命名空間
PHP核心探索:定義介面
PHP核心探索:繼承與實現介面
PHP核心探索:資源resource類型
PHP核心探索:Zend虛擬機器
PHP核心探索:虛擬機器的詞法解析
PHP核心探索:虛擬機器的文法分析
PHP核心探索:中間代碼opcode的執行
PHP核心探索:代碼的加密與解密
PHP核心探索:zend_execute的具體執行過程
PHP核心探索:變數的引用與計數規則
PHP核心探索:新記憶體回收機制說明

參考來源:
PHP核心探索:zend_execute的具體執行過程
http://www.lai18.com/content/425167.html

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.