PHP カーネルの探索: zend_execute の具体的な実行プロセス
インタプリタ エンジンが最終的に実行する関数は zend_execute です。実際、zend_execute は、エンジンの初期化時に実行される関数ポインタです。 in {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"); }
この関数のパラメータは、コンパイルプロセス中に生成される 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 構造体です。たとえば、ユーザー定義関数は、グローバル関数シンボル テーブルである GLOBAL_FUNCTION_TABLE に保存され、関数名を通じてこのテーブルから取得できます。では、コンパイル後に返される zend_op_array ポインタは何でしょうか? 実際、コンパイル後に返される zend_op_array は、最も外側の層、つまりどの関数にも含まれていないグローバル コードで構成される op_array とみなすこともできます。体。ただし、グローバル コード、ユーザー定義関数、およびユーザー定義メソッドはすべて同じ型値を持ちます。 2. 型の可能な値のマクロ定義は次のとおりです:
#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 に対応しており、これは最も一般的なタイプでもあります。 ZEND_EVAL_CODE は eval 関数の PHP コードに対応するため、eval 関数パラメータの PHP コードも別個にコンパイルされることが想像できます。 zend_op_array。
2.function_name
op_array がユーザー定義関数またはメソッドのコンパイルによって生成される場合、このフィールドは関数の名前に対応します。それがグローバル コードまたは eval 部分のコードの場合、このフィールドは control です。
3.opcodes
このフィールドの型は zend_op * であるため、これは zend_op の配列です。zend_op について知らない場合は、前の記事「OPcode の概要」を参照してください。 、このフィールドは最も重要な部分です。zend_execute はここに保存された操作を最終的に実行します。
パラメータ op_array の基本を理解したので、実行を開始します。
execute 関数は、いくつかの基本的な変数の宣言から始まります。その中の zend_execute_data *execute_data; この変数は、特定の初期化後に各操作に渡されます。 handler 関数はパラメータとして使用され、op は実行中にいつでもexecute_dataの内容を変更できます。
14 行目 zend_vm_enter このジャンプ ラベルは、仮想マシン実行のエントリ ポイントとして使用されます。op に関数呼び出しが含まれる場合、関数本体を実行するためにここにジャンプすることがあります。
16行目から19行目はexecute_data用の領域を確保しています
21行目から32行目は主にexecute_dataの初期化と現場作業の保存を行っています。 current some 関数呼び出し終了後に動作中のデータが復元されることは、プロセスが呼び出されたときにレジスタなどのコンテキスト環境を保存する必要があるオペレーティング システムのプロセス スケジューリングとして想像できます。では、実行を続行するために取り出されます。
行 41 から 51 は主に $this 変数を現在の動的シンボル テーブルに追加します。これはオブジェクトのメソッドを呼び出す場合にのみ必要です。
58 行目で始まる while 無限ループは、op_array 内のオペコードの実行を開始します。 66 行目では、現在実行されている演算のハンドラーが呼び出されます。
EX(opline)->handler(execute_data TSRMLS_CC))
その後、ハンドラーの戻り値が0、ループは継続します。0 より大きい場合、スイッチ構造に入ります。
戻り値が 1 の場合: 実行関数が戻り、実行が終了します。
戻り値が 2 の場合: op_array はリセットされ、zend_vm_enter にジャンプします。これは通常、関数呼び出しであるか、関連する関数の op_array が新しいコンテキストで実行されます。値は 3 時です: ループ本体は実行を続けます。もちろん、実行を続ける前に、EX (opline) は 1 つ前の位置 (おそらく複数の位置) に移動されます。これは、後ろの新しい opline を指したことを意味します。 , そのため、新しい opline は実行され続けます
他の値を返す場合: ループを終了し、エラーを報告し、return で終了します。つまり、return 1 です
op のハンドラーで返される特定の値はマクロとして定義されており、 {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函数的执行环境。
下面我们将以一段简单的代码来介绍执行环境切换过程,例子代码:
<?php $a = 123; test(); function test() { return 1; } ?>
这段代码非常简单,这样方便我们介绍原理,复杂的代码读者可以举一反三。此代码编译之后会生成两个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 Engine
PHP カーネル探索: SAPI 再訪
PHP カーネル探索: Apache モジュールの概要
PHP カーネル探索: mod_php5 による PHP サポート
PHP カーネルの探索: フック関数を使用した Apache ランタイム
PHP カーネルの探索: 組み込み PHP
PHP カーネルの探索: PHP 用 FastCGI
PHP カーネルの探索: PHP スクリプトの実行方法
PHP カーネルの探索: PHP スクリプトの実行の詳細
PHP カーネルの探索: OpCode
PHP カーネルの探索: PHP のオペコード
PHP カーネルの探索: インタプリタの実行プロセス
PHP カーネルの探索: 変数の概要
PHP カーネルの探索: 変数のストレージと型
PHP カーネルの探索: PHP のハッシュ テーブル
PHP カーネルの探索: Zend のハッシュ テーブルについて理解する
PHP カーネルの探索: PHP ハッシュ アルゴリズムの設計
PHP カーネルの探索: HashTables 記事の翻訳
PHP カーネルの探索: ハッシュ衝突攻撃とは何ですか?
PHP カーネルの探索: 定数の実装
PHP カーネルの探索: 変数の保存
PHP カーネルの探索: 変数の種類
PHP カーネルの探索: 変数の値操作
PHP カーネルの探索: 変数の作成
PHP カーネルの探索: 事前定義された変数
PHPカーネル探索: 変数の取得
PHP カーネル探索: 変数の型変換
PHP カーネル探索: 弱く型付けされた変数の実装
PHP カーネル探索: 静的変数の実装
PHP カーネル探索: 変数の型のヒント
PHP カーネル探索: V変数のライフサイクル
PHP カーネルの探索: 変数の代入と破棄
PHP カーネルの探索: 変数のスコープ
PHP カーネルの探索: 奇妙な変数名
PHP カーネルの探索: 変数の値と型の保存
PHP カーネルの探索: グローバル変数 グローバル
PHP カーネルの探索: 変数の変換タイプ
PHP カーネルの探索: メモリ管理の始まり
PHP カーネルの探索: Zend Memory Manager
PHP カーネルの探索: PHP のメモリ管理
PHP カーネルの探索: メモリの適用と破壊
PHP カーネルの探索: 参照カウントとコピーオンライト
PHP カーネル探索: PHP5.3 のガベージ コレクション メカニズム
PHP カーネル探索: メモリ管理内のキャッシュ
PHP カーネル探索: コピーオンライト COW メカニズム
PHP カーネル探索: 配列とリンク リスト
PHP カーネル探索: Ha ハッシュ テーブル API の使用
PHPカーネル探索:配列操作
PHPカーネル探索:配列ソースコード解析
PHPカーネル探索:関数の分類
PHPカーネル探索:関数の内部構造
PHPカーネル探索:関数構造変換
PHPカーネル探索:関数の定義 プロセス
PHP カーネル探索: 関数パラメータ
PHP カーネル探索: zend_parse_parameters 関数
PHP カーネル探索: 関数戻り値
PHP カーネル探索: 仮パラメータ戻り値
PHP カーネル探索: 関数呼び出しと実行
PHP カーネル探索: 参照と関数実行
PHP コアの探索: 匿名関数とクロージャ
PHP コアの探索: オブジェクト指向の始まり
PHP コアの探索: クラスの構造と実装
PHP コアの探索: クラスのメンバー変数
PHP コアの探索: クラスのメンバー メソッド
PHPカーネルの探索: クラス zend_class_entry のプロトタイプ
PHP カーネルの探索: クラスの定義
PHP カーネルの探索: アクセス制御
PHP カーネルの探索: 継承、ポリモーフィズム、抽象クラス
PHP カーネルの探索: マジック関数と遅延バインディング
PHP カーネルの探索: 予約クラスと特殊クラス
PHP コアの探索: オブジェクト
PHP コアの探索: オブジェクト インスタンスの作成
PHP コアの探索: オブジェクト属性の読み取りと書き込み
PHP コアの探索: ネームスペース
PHP コアの探索: インターフェイスの定義
PHP コアの探索: 継承と実装インターフェース
PHP カーネル探索: リソース リソース タイプ
PHP カーネル探索: Zend 仮想マシン
PHP カーネル探索: 仮想マシンの字句解析
PHP カーネル探索: 仮想マシンの構文解析
PHP カーネル探索: 中間コード オペコードの実行
PHPカーネル探索: コードの暗号化と復号
PHP カーネル探索: zend_execute の具体的な実行プロセス
PHP カーネル探索: 変数の参照とカウント規則
PHP カーネル探索: 新しいガベージ コレクション機構の説明
参照元:
PHP カーネルの探索 : zend_execute の具体的な実行プロセス
http://www.lai18.com/content/425167.html