<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.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關系: ![](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類型不需要),通過引用計數管理,當在被調函數內部改寫參數的值時將重新拷貝一份,與普通的變量用法相同。 ![](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; } ``` ![](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>

                              哎呀哎呀视频在线观看