<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>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ### 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分配的內存釋放,這個過程會將所有的局部變量"銷毀",執行階段結束 ![](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`,而在調用內部函數時則只需要分配實際傳入參數的空間即可,內部函數不會有臨時變量的概念。 最終分配的內存空間如下圖: ![](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); } ```
                  <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>

                              哎呀哎呀视频在线观看