<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                ## 3.3 Zend引擎執行過程 Zend引擎主要包含兩個核心部分:編譯、執行: ![zend_vm](https://box.kancloud.cn/80085d6bbeff1e8be01d4ec7fd2c091f_442x127.png) 前面分析了Zend的編譯過程以及PHP用戶函數的實現,接下來分析下Zend引擎的執行過程。 ### 3.3.1 數據結構 執行流程中有幾個重要的數據結構,先看下這幾個結構。 #### 3.3.1.1 opcode opcode是將PHP代碼編譯產生的Zend虛擬機可識別的指令,php7共有173個opcode,定義在`zend_vm_opcodes.h`中,PHP中的所有語法實現都是由這些opcode組成的。 ```c 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; //返回值類型 }; ``` #### 3.3.1.2 zend_op_array `zend_op_array`是Zend引擎執行階段的輸入,整個執行階段的操作都是圍繞著這個結構,關于其具體結構前面我們已經講過了。 ![zend_op_array](https://box.kancloud.cn/985c86ac9e827c2a05ed38d12c369fd0_436x245.png) 這里再重復說下zend_op_array幾個核心組成部分: * __opcode指令__:即PHP代碼具體對應的處理動作,與二進制程序中的代碼段對應 * __字面量存儲__:PHP代碼中定義的一些變量初始值、調用的函數名稱、類名稱、常量名稱等等稱之為字面量,這些值用于執行時初始化變量、函數調用等等 * __變量分配情況__:與字面量類似,這里指的是當前opcodes定義了多少變量、臨時變量,每個變量都有一個對應的編號,執行初始化按照總的數目一次性分配zval,使用時也完全按照編號索引,而不是根據變量名索引 #### 3.3.1.3 zend_executor_globals `zend_executor_globals executor_globals`是PHP整個生命周期中最主要的一個結構,是一個全局變量,在main執行前分配(非ZTS下),直到PHP退出,它記錄著當前請求全部的信息,經常見到的一個宏`EG`操作的就是這個結構。 ```c //zend_compile.c #ifndef ZTS ZEND_API zend_compiler_globals compiler_globals; ZEND_API zend_executor_globals executor_globals; #endif //zend_globals_macros.h # define EG(v) (executor_globals.v) ``` `zend_executor_globals`結構非常大,定義在`zend_globals.h`中,比較重要的幾個字段含義如下圖所示: ![EG](https://box.kancloud.cn/dc6fee4115fea836048e332361ba7d7b_960x777.png) #### 3.3.1.4 zend_execute_data `zend_execute_data`是執行過程中最核心的一個結構,每次函數的調用、include/require、eval等都會生成一個新的結構,它表示當前的作用域、代碼的執行位置以及局部變量的分配等等,等同于機器碼執行過程中stack的角色,后面分析具體執行流程的時候會詳細分析其作用。 ```c #define EX(element) ((execute_data)->element) //zend_compile.h struct _zend_execute_data { const zend_op *opline; //指向當前執行的opcode,初始時指向zend_op_array起始位置 zend_execute_data *call; /* current call */ zval *return_value; //返回值指針 zend_function *func; //當前執行的函數(非函數調用時為空) zval This; //這個值并不僅僅是面向對象的this,還有另外兩個值也通過這個記錄:call_info + num_args,分別存在zval.u1.reserved、zval.u2.num_args zend_class_entry *called_scope; //當前call的類 zend_execute_data *prev_execute_data; //函數調用時指向調用位置作用空間 zend_array *symbol_table; //全局變量符號表 #if ZEND_EX_USE_RUN_TIME_CACHE void **run_time_cache; /* cache op_array->run_time_cache */ #endif #if ZEND_EX_USE_LITERALS zval *literals; //字面量數組,與func.op_array->literals相同 #endif }; ``` zend_execute_data與zend_op_array的關聯關系: ![zend_ex_op](https://box.kancloud.cn/005c7ed47923adba803d0f313417ad5f_450x676.png) ### 3.3.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`的作用。 注意:在執行前分配內存時并不僅僅是分配了`zend_execute_data`大小的空間,除了`sizeof(zend_execute_data)`外還會額外申請一塊空間,用于分配局部變量、臨時(中間)變量等,具體的分配過程下面會講到。 __Zend執行opcode的簡略過程:__ * __step1:__ 為當前作用域分配一塊內存,充當運行棧,zend_execute_data結構、所有局部變量、中間變量等等都在此內存上分配 * __step2:__ 初始化全局變量符號表,然后將全局執行位置指針EG(current_execute_data)指向step1新分配的zend_execute_data,然后將zend_execute_data.opline指向op_array的起始位置 * __step3:__ 從EX(opline)開始調用各opcode的C處理handler(即_zend_op.handler),每執行完一條opcode將`EX(opline)++`繼續執行下一條,直到執行完全部opcode,函數/類成員方法調用、if的執行過程: * __step3.1:__ if語句將根據條件的成立與否決定`EX(opline) + offset`所加的偏移量,實現跳轉 * __step3.2:__ 如果是函數調用,則首先從EG(function_table)中根據function_name取出此function對應的編譯完成的zend_op_array,然后像step1一樣新分配一個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),釋放分配的運行棧,銷毀局部變量,繼續從原來函數調用的位置執行 * __step3.3:__ 類方法的調用與函數基本相同,后面分析對象實現的時候再詳細分析 * __step4:__ 全部opcode執行完成后將step1分配的內存釋放,這個過程會將所有的局部變量"銷毀",執行階段結束 ![zend_execute](https://box.kancloud.cn/36e6668ac66e32b8e723c606ce0f65de_994x823.png) 接下來詳細看下整個流程。 Zend執行入口為位于`zend_vm_execute.h`文件中的__zend_execute()__: ```c ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value) { zend_execute_data *execute_data; if (EG(exception) != NULL) { return; } //分配zend_execute_data execute_data = zend_vm_stack_push_call_frame(ZEND_CALL_TOP_CODE, (zend_function*)op_array, 0, zend_get_called_scope(EG(current_execute_data)), zend_get_this_object(EG(current_execute_data))); if (EG(current_execute_data)) { execute_data->symbol_table = zend_rebuild_symbol_table(); } else { execute_data->symbol_table = &EG(symbol_table); } EX(prev_execute_data) = EG(current_execute_data); //=> execute_data->prev_execute_data = EG(current_execute_data); i_init_execute_data(execute_data, op_array, return_value); //初始化execute_data zend_execute_ex(execute_data); //執行opcode zend_vm_stack_free_call_frame(execute_data); //釋放execute_data:銷毀所有的PHP變量 } ``` 上面的過程分為四步: #### (1)分配stack 由`zend_vm_stack_push_call_frame`函數分配一塊用于當前作用域的內存空間,返回結果是`zend_execute_data`的起始位置。 ```c //zend_execute.h static zend_always_inline zend_execute_data *zend_vm_stack_push_call_frame(uint32_t call_info, zend_function *func, uint32_t num_args, ...) { uint32_t used_stack = zend_vm_calc_used_stack(num_args, func); return zend_vm_stack_push_call_frame_ex(used_stack, call_info, func, num_args, called_scope, object); } ``` 首先根據`zend_execute_data`、當前`zend_op_array`中局部/臨時變量數計算需要的內存空間: ```c //zend_execute.h static zend_always_inline uint32_t zend_vm_calc_used_stack(uint32_t num_args, zend_function *func) { uint32_t used_stack = ZEND_CALL_FRAME_SLOT + num_args; //內部函數只用這么多,臨時變量是編譯過程中根據PHP的代碼優化出的值,比如:`"hi~".time()`,而在內部函數中則沒有這種情況 if (EXPECTED(ZEND_USER_CODE(func->type))) { //在php腳本中寫的function used_stack += func->op_array.last_var + func->op_array.T - MIN(func->op_array.num_args, num_args); } return used_stack * sizeof(zval); } //zend_compile.h #define ZEND_CALL_FRAME_SLOT \ ((int)((ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval)) - 1) / ZEND_MM_ALIGNED_SIZE(sizeof(zval)))) ``` 回想下前面編譯階段zend_op_array的結果,在編譯過程中已經確定當前作用域下有多少個局部變量(func->op_array.last_var)、臨時/中間/無用變量(func->op_array.T),從而在執行之初就將他們全部分配完成: * __last_var__:PHP代碼中定義的變量數,zend_op.op{1|2}_type = IS_CV 或 result_type & IS_CV的全部數量 * __T__:表示用到的臨時變量、無用變量等,zend_op.op{1|2}_type = IS_TMP_VAR|IS_VAR 或resulte_type & (IS_TMP_VAR|IS_VAR)的全部數量 比如賦值操作:`$a = 1234;`,編譯后`last_var = 1,T = 1`,`last_var`有`$a`,這里為什么會有`T`?因為賦值語句有一個結果返回值,只是這個值沒有用到,假如這么用結果就會用到了`if(($a = 1234) == true){...}`,這時候`$a = 1234;`的返回結果類型是`IS_VAR`,記在`T`上。 `num_args`為函數調用時的實際傳入參數數量,`func->op_array.num_args`為全部參數數量,所以`MIN(func->op_array.num_args, num_args)`等于`num_args`,在自定義函數中`used_stack=ZEND_CALL_FRAME_SLOT + func->op_array.last_var + func->op_array.T`,而在調用內部函數時則只需要分配實際傳入參數的空間即可,內部函數不會有臨時變量的概念。 最終分配的內存空間如下圖: ![var_T](https://box.kancloud.cn/cd3eff89c3b09a389d91e77a12cf688e_614x258.png) 這里實際分配內存時并不是直接`malloc`的,還記得上面EG結構中有個`vm_stack`嗎?實際內存是從這里獲取的,每次從`EG(vm_stack_top)`處開始分配,分配完再將此指針指向`EG(vm_stack_top) + used_stack`,這里不再對vm_stack作更多分析,更下層實際就是Zend的內存池(zend_alloc.c),后面也會單獨分析。 ```c static zend_always_inline zend_execute_data *zend_vm_stack_push_call_frame_ex(uint32_t used_stack, ...) { zend_execute_data *call = (zend_execute_data*)EG(vm_stack_top); ... //當前vm_stack是否夠用 if (UNEXPECTED(used_stack > (size_t)(((char*)EG(vm_stack_end)) - (char*)call))) { call = (zend_execute_data*)zend_vm_stack_extend(used_stack); //新開辟一塊vm_stack ... }else{ //空間夠用,直接分配 EG(vm_stack_top) = (zval*)((char*)call + used_stack); ... } call->func = func; ... return call; } ``` #### (2)初始化zend_execute_data 注意,這里的初始化是整個php腳本最初的那個,并不是指函數調用時的,這一步的操作主要是設置幾個指針:`opline`、`call`、`return_value`,同時將PHP的全局變量添加到`EG(symbol_table)`中去: ```c //zend_execute.c static zend_always_inline void i_init_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value) { EX(opline) = op_array->opcodes; EX(call) = NULL; EX(return_value) = return_value; if (UNEXPECTED(EX(symbol_table) != NULL)) { ... zend_attach_symbol_table(execute_data);//將全局變量添加到EG(symbol_table)中一份,因為此處的execute_data是PHP腳本最初的那個,不是function的,所以所有的變量都是全局的 }else{ //這個分支的情況還未深入分析,后面碰到再補充 ... } } ``` `zend_attach_symbol_table()`的作用是把當前作用域下的變量添加到EG(symbol_table)哈希表中,也就是全局變量,函數中通過global關鍵詞獲取的全局變量正是在此時添加的,EG(symbol_table)中的值間接的指向`zend_execute_data`中的局部變量,兩者的結構如下圖所示: ![](https://box.kancloud.cn/f987ea23edefcf51d2333d3deeee3fe2_739x206.png) #### (3)執行opcode 這一步開始具體執行opcode指令,這里調用的是`zend_execute_ex`,這是一個函數指針,如果此指針沒有被任何擴展重新定義那么將由默認的`execute_ex`處理: ```c # define ZEND_OPCODE_HANDLER_ARGS_PASSTHRU execute_data ZEND_API void execute_ex(zend_execute_data *ex) { zend_execute_data *execute_data = ex; while(1) { int ret; if (UNEXPECTED((ret = ((opcode_handler_t)EX(opline)->handler)(execute_data /*ZEND_OPCODE_HANDLER_ARGS_PASSTHRU*/)) != 0)) { if (EXPECTED(ret > 0)) { //調到新的位置執行:函數調用時的情況 execute_data = EG(current_execute_data); }else{ return; } } } } ``` 大概的執行過程上面已經介紹過了,這里只分析下整體執行流程,至于PHP各語法具體的handler處理后面會單獨列一章詳細分析。 #### (4)釋放stack 這一步就比較簡單了,只是將申請的`zend_execute_data`內存釋放給內存池(注意這里并不是變量的銷毀),具體的操作只需要修改幾個指針即可: ```c static zend_always_inline void zend_vm_stack_free_call_frame_ex(uint32_t call_info, zend_execute_data *call) { ZEND_ASSERT_VM_STACK_GLOBAL; if (UNEXPECTED(call_info & ZEND_CALL_ALLOCATED)) { zend_vm_stack p = EG(vm_stack); zend_vm_stack prev = p->prev; EG(vm_stack_top) = prev->top; EG(vm_stack_end) = prev->end; EG(vm_stack) = prev; efree(p); } else { EG(vm_stack_top) = (zval*)call; } ZEND_ASSERT_VM_STACK_GLOBAL; } static zend_always_inline void zend_vm_stack_free_call_frame(zend_execute_data *call) { zend_vm_stack_free_call_frame_ex(ZEND_CALL_INFO(call), call); } ``` ### 3.3.3 函數的執行流程 (這里的函數指用戶自定義的PHP函數,不含內部函數) 上一節我們介紹了zend執行引擎的幾個關鍵步驟,也簡單的介紹了函數的調用過程,這里再單獨總結下: * __【初始化階段】__ 這個階段首先查找到函數的zend_function,普通function就是到EG(function_table)中查找,成員方法則先從EG(class_table)中找到zend_class_entry,然后再進一步在其function_table找到zend_function,接著就是根據zend_op_array新分配 __zend_execute_data__ 結構并設置上下文切換的指針 * __【參數傳遞階段】__ 如果函數沒有參數則跳過此步驟,有的話則會將函數所需參數傳遞到 __初始化階段__ 新分配的 __zend_execute_data動態變量區__ * __【函數調用階段】__ 這個步驟主要是做上下文切換,將執行器切換到調用的函數上,可以理解會在這個階段__遞歸調用zend_execute_ex__函數實現call的過程(實際并一定是遞歸,默認是在while(1){...}中切換執行空間的,但如果我們在擴展中重定義了zend_execute_ex用來介入執行流程則就是遞歸調用) * __【函數執行階段】__ 被調用函數內部的執行過程,首先是接收參數,然后開始執行opcode * __【函數返回階段】__ 被調用函數執行完畢返回過程,將返回值傳遞給調用方的zend_execute_data變量區,然后釋放zend_execute_data以及分配的局部變量,將上下文切換到調用前,回到調用的位置繼續執行,這個實際是函數執行中的一部分,不算是獨立的一個過程 接下來我們一個具體的例子詳細分析下各個階段的處理過程: ```php function my_function($a, $b = false, $c = "hi"){ return $c; } $a = array(); $b = true; my_function($a, $b); ``` 主腳本、my_function的opcode為: ![](https://box.kancloud.cn/57b7a89d107c648e1bef5ab113a70e45_664x312.png) #### 3.3.3.1 初始化階段 此階段的主要工作有兩個:查找函數zend_function、分配zend_execute_data。 上面的例子此過程執行的opcode為`ZEND_INIT_FCALL`,根據op_type計算可得handler為`ZEND_INIT_FCALL_SPEC_CONST_HANDLER`: ```c static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_FCALL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE zval *fname = EX_CONSTANT(opline->op2); //調用的函數名稱通過操作數2記錄 zval *func; zend_function *fbc; zend_execute_data *call; //這里牽扯到zend的一種緩存機制:運行時緩存,后面我們會單獨分析,這里忽略即可 ... //首先根據函數名去EG(function_table)索引zend_function func = zend_hash_find(EG(function_table), Z_STR_P(fname)); if (UNEXPECTED(func == NULL)) { SAVE_OPLINE(); zend_throw_error(NULL, "Call to undefined function %s()", Z_STRVAL_P(fname)); HANDLE_EXCEPTION(); } fbc = Z_FUNC_P(func); //(*func).value.func ... //分配zend_execute_data call = zend_vm_stack_push_call_frame_ex( opline->op1.num, ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, NULL, NULL); call->prev_execute_data = EX(call); EX(call) = call; //將當前正在運行的zend_execute_data.call指向新分配的zend_execute_data ZEND_VM_NEXT_OPCODE(); } ``` 當前zend_execute_data及新生成的zend_execute_data關系: ![zend_exe_init](https://box.kancloud.cn/18a83aef75e7263a5d055bf07148732f_746x394.png) 注意 __This__ 這個值,它并不僅僅指的是面向對象中那個this,此外它還記錄著其它兩個信息: * __call_info:__ 調用信息,通過 __This.u1.reserved__ 記錄,因為我們的主腳本、用戶自定義函數調用、內核函數調用、include/require/eval等都會生成一個zend_execute_data,這個值就是用來區分這些不同類型的,對應的具體值為:ZEND_CALL_TOP_CODE、ZEND_CALL_NESTED_FUNCTION、ZEND_CALL_TOP_FUNCTION、ZEND_CALL_NESTED_CODE,這個信息是在分配zend_execute_data時顯式聲明的 * __num_args:__ 函數調用實際傳入的參數數量,通過 __This.u2.num_args__ 記錄,比如示例中我們定義的函數有3個參數,其中1個是必傳的,而我們調用時傳入了2個,所以這個例子中的num_args就是2,這個值在編譯時知道的,保存在 __zend_op->extended_value__ 中 #### 3.3.3.2 參數傳遞階段 這個過程就是將當前作用空間下的變量值"復制"到新的zend_execute_data動態變量區中,那么調用方怎么知道要把值傳遞到新zend_execute_data哪個位置呢?實際這個地方是有固定規則的,zend_execute_data的動態變量區最前面是參數變量,按照參數的順序依次分配,接著才是普通的局部變量、臨時變量等,所以調用方就可以根據傳的是第幾個參數來確定其具體的存儲位置。 另外這里的"復制"并不是硬拷貝,而是傳遞的value指針(當然bool/int/double類型不需要),通過引用計數管理,當在被調函數內部改寫參數的值時將重新拷貝一份,與普通的變量用法相同。 ![func_exe_send_var](https://box.kancloud.cn/0bb7cdac91057457510d5186757af7c5_653x344.png) 圖中畫的只是上面示例那種情況,比如`my_function(array());`直接傳值則會是 __literals區->新zend_execute_data動態變量區__ 的傳遞。 #### 3.3.3.3 函數調用階段 這個過程主要是進行一些上下文切換,將執行器切換到調用的函數上。 上面例子對應的opcode為`ZEND_DO_UCALL`,handler為`ZEND_DO_UCALL_SPEC_HANDLER`: ```c static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_UCALL_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE zend_execute_data *call = EX(call); zend_function *fbc = call->func; zval *ret; SAVE_OPLINE(); EX(call) = call->prev_execute_data; EG(scope) = NULL; ret = NULL; call->symbol_table = NULL; if (RETURN_VALUE_USED(opline)) { ret = EX_VAR(opline->result.var); //函數返回值的存儲位置 ZVAL_NULL(ret); Z_VAR_FLAGS_P(ret) = 0; } call->prev_execute_data = execute_data; //將新zend_execute_data->prev_execute_data指向當前data i_init_func_execute_data(call, &fbc->op_array, ret, 0); ZEND_VM_ENTER(); } //zend_execute.c static zend_always_inline void i_init_func_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value, int check_this) { uint32_t first_extra_arg, num_args; ZEND_ASSERT(EX(func) == (zend_function*)op_array); EX(opline) = op_array->opcodes; EX(call) = NULL; EX(return_value) = return_value; first_extra_arg = op_array->num_args; //函數的總參數數量,示例中為3 num_args = EX_NUM_ARGS(); //實際傳入參數數量,示例中為2 if (UNEXPECTED(num_args > first_extra_arg)) { ... } else if (EXPECTED((op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0)) { //跳過前面幾個已經傳參的參數接收的指令,因為已經顯式的傳遞參數了,無需再接收默認值 EX(opline) += num_args; } //初始化動態變量區,將所有變量(除已經傳入的外)設置為IS_UNDEF if (EXPECTED((int)num_args < op_array->last_var)) { zval *var = EX_VAR_NUM(num_args); zval *end = EX_VAR_NUM(op_array->last_var); do { ZVAL_UNDEF(var); var++; } while (var != end); } ... //分配運行時緩存,此機制后面再單獨說明 if (UNEXPECTED(!op_array->run_time_cache)) { op_array->run_time_cache = zend_arena_alloc(&CG(arena), op_array->cache_size); memset(op_array->run_time_cache, 0, op_array->cache_size); } EX_LOAD_RUN_TIME_CACHE(op_array); //execute_data.run_time_cache = op_array.run_time_cache EX_LOAD_LITERALS(op_array); //execute_data.literals = op_array.literals //EG(current_execute_data)為執行器當前執行空間,將執行器切到函數內 EG(current_execute_data) = execute_data; } ``` ![func_call](https://box.kancloud.cn/4171ebae6bccd9858e336f6ac422ca46_508x393.png) #### 3.3.3.4 函數執行階段 這個過程就是函數內部opcode的執行流程,沒什么特別的,唯一的不同就是前面會接收未傳的參數,如下圖所示。 ![](https://box.kancloud.cn/af2c564e5b27965ce15616baa04bcb30_363x232.png) #### 3.3.3.5 函數返回階段 實際此過程可以認為是3.3.3.4的一部分,這個階段就是函數調用結束,返回調用處的過程,這個過程中有三個關鍵工作:拷貝返回值、執行器切回調用位置、釋放清理局部變量。 上面例子此過程opcode為`ZEND_RETURN`,對應的handler為`ZEND_RETURN_SPEC_CV_HANDLER`: ```c static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RETURN_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE zval *retval_ptr; zend_free_op free_op1; //獲取返回值 retval_ptr = _get_zval_ptr_cv_undef(execute_data, opline->op1.var); if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) { //返回值未定義,返回NULL retval_ptr = GET_OP1_UNDEF_CV(retval_ptr, BP_VAR_R); if (EX(return_value)) { ZVAL_NULL(EX(return_value)); } } else if(!EX(return_value)){ //無返回值 ... }else{ //返回值正常 ... ZVAL_DEREF(retval_ptr); //如果retval_ptr是引用則將找到其具體引用的zval ZVAL_COPY(EX(return_value), retval_ptr); //將返回值復制給調用方接收值:EX(return_value) ... } ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); } ``` 繼續看下`zend_leave_helper_SPEC`,執行器切換、局部變量清理就是在這個函數中完成的。 ```c static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS) { zend_execute_data *old_execute_data; uint32_t call_info = EX_CALL_INFO(); if (EXPECTED(ZEND_CALL_KIND_EX(call_info) == ZEND_CALL_NESTED_FUNCTION)) { //普通的函數調用將走到這個分支 i_free_compiled_variables(execute_data); ... } //include、eval及整個腳本的結束(main函數)走到下面 //... //將執行器切回調用的位置 EG(current_execute_data) = EX(prev_execute_data); } //zend_execute.c //清理局部變量的過程 static zend_always_inline void i_free_compiled_variables(zend_execute_data *execute_data) { zval *cv = EX_VAR_NUM(0); zval *end = cv + EX(func)->op_array.last_var; while (EXPECTED(cv != end)) { if (Z_REFCOUNTED_P(cv)) { if (!Z_DELREF_P(cv)) { //引用計數減一后為0 zend_refcounted *r = Z_COUNTED_P(cv); ZVAL_NULL(cv); zval_dtor_func_for_ptr(r); //釋放變量值 } else { GC_ZVAL_CHECK_POSSIBLE_ROOT(cv); //引用計數減一后>0,啟動垃圾檢查機制,清理循環引用導致無法回收的垃圾 } } cv++; } } ``` 除了函數調用完成時有return操作,其它還有兩種情況也會有此過程: * __1.PHP主腳本執行結束時:__ 也就是PHP腳本開始執行的入口腳本(PHP沒有顯式的main函數,這種就可以認為是main函數),但是這種情況并不會在return時清理,因為在main函數中定義的變量并非純碎的局面變量,它們都是全局變量,與$__GET、$__POST是一類,這些全局變量的清理是在request_shutdown階段處理 * __2.include、eval:__ 以include為例,如果include的文件中定義了全局變量,那么這些變量實際與上面1的情況一樣,它們的存儲位置是在一起的 所以實際上面說的這兩種情況屬于一類,它們并不是局部變量的清理,而是 __全局變量的清理__ ,另外局部變量的清理也并非只有return一個時機,另外還有一個更重要的時機就是變量分離時,這種情況我們在《PHP語法實現》一節再具體說明。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看