編譯型語言php
對於C語言,C++,編譯成機器碼(二進制)來運行。
Java語言,把.java
編譯成.class
, 稱爲bytecode(字節碼),由jvm來運行java
解釋型語言linux
解釋器解釋執行。 典型的如: linux shellshell
解釋器逐行來執行命令apache
PHP執行api
PHP是先編譯後執行數組
PHP稍有特殊,雖然是一個腳本語言,但不是靠解釋器解釋。而是zend虛擬機執行,屏蔽了操做系統的區別。緩存
PHP代碼編譯成 opcode
,由zend虛擬機來執行opcode
。服務器
可是opcode
,PHP腳本一結束,opcode
就清除了。jvm
opcode 可否緩存
PHP自己不支持,可是apc,xcache等加速器,實現了這樣的效果。
PHP底層是C語言來實現的,C語言是強類型,而PHP是弱類型語言,是如何實現的
PHP的源碼包:
|__ ext |__ main |__ pear |__ sapi |__ tests |__ TSRM |__ Zend |__ .gdbinit
最核心的是Zend
,這是zend虛擬的實現。包括棧,數據類型,編譯器等.
最重要的main
,PHP的一些內建函數,最重要的函數都在該目錄下.
最大的一個目錄 ext
, PHP的擴展.
PHP的大部分功能,都是以extenstion形式來完成的。
若是自身開發了一個擴展,也放入ext目錄下。
弱類型語言變量的實現
/* zend.h */ struct _zval_struct { zvalue_value value; /* 值 */ zend_uint refcount__gc; zend_uchar type; /* 活動類型 */ zend_uchar is_ref__gc; }
PHP中的一個變量,zend虛擬機中,使用的是 _zval_struct
的結構體來描述,變量的值也是一個就結構體來描述.
_zval_struct
的結構體是由 四個字段/域 (能夠理解成關聯數組)
zvalue_value value; /* 值 */
PHP變量的值,存儲這個字段中。
具體存儲的位置:
/* value 值 是一個 聯合 */ /* zend.h */ typedef union _zval_value { long lval; /* long value */ double dval; /* double value */ struct { char * val; int len; } str; HashTable *ht; /* hash table 指針 */ zend_object_value obj; } zvalue_value;
Zend對變量的表示
zend實現了 zval結構體
{ value: [聯合體] /* 聯合體的內容多是C語言中的long,double,hashtable(*ht),obj, 聯合體只能是其中一種類型,是一個枚舉 */ type: 變量類型 , /* IS_NULL,IS_BOOL,IS_STRING, IS_LONG,IS_DOUBLE,IS_ARRAY,IS_OBJECT,IS_RESOURCE */ refcount_gc is_ref_gc }
C語言中類型對應PHP中的數據類型:
long -> int double -> double hashtable -> array struct -> string obj -> object
例如:
$a = 3; { value: [long lval = 3] type: IS_LONG } $a = 3.5; { value: [double dval = 3.5] type: IS_DOUBLE }
zend_uchar type; /* 活動類型 */
能夠根據上下文環境來強制轉換。
例如:須要echo 的時候 就轉換成 string
須要加減運算就 轉換成 int
PHP 中有8中數據類型,爲何zval->value 聯合體中,只有5中 ?
1: NULL,直接 zval->type = IS_NULL, 就能夠表示,沒必要設置 value 的值。
2:BOOL, zval->type = IS_BOOL. 再設置 zval.value.lval = 1/0; (C語言中沒有布爾值,都是經過1,0,來表示)
3: resource ,資源型,每每是服務器上打開一個接口,若是 文件讀取接口。 zval->type = IS_RESOURCE, zval->type.lval = 服務器上打開的接口編號。
struct { char * val; int len; } str;
PHP中,字符串類型,長度是已經緩存的,調用strlen時,系統能夠直接返回其長度,不須要計算。
$b = 'hello'; /** * * { * union_zvalue { * // 字符串的指針 * struct{ * char: 'hello'; * len: 5 * } str; * } * type: IS_STRING; * refcount_gc: 1, * is_ref_gc: 0 * } * */ //在PHP中字符串的長度,是直接體如今其結構體中,因此調用strlen(); 速度很是快,時間複雜度爲0(1) echo strlen($b);
符號表symbol_table,變量的花名冊
符號表是什麼?
符號表示一張哈希表(哈希結構理解成關聯數組)
裏面存儲了變量名-> 變量zval結構體的地址
struct _zend_executor_globals { ... ... HashTable * active_symbol_table /* 活動符號表 */ HashTable symbol_table /* 全局符號表 */ HashTable included_files; /* files already included */ }
// 變量花名冊 $a = 3; $b = 1.223; $c = 'hello'; /** * * 生成了3個結構體 * 同時,全局符號表,中多了三條記錄 * * a ---> 0x123 ---> 結構體 { 3 } * b ---> 0x21a ---> 結構體 { 1.223 } * c ---> 0x1A0 ---> 結構體 { hello } * */ // 變量聲明 // 第一:結構體生成 // 第二:符號表中多了記錄,變量的花名冊 // 第三:指向結構體
傳值賦值發生了什麼
在傳值賦值時:
以:$a = 3; $b = $a;爲例:
並無再次產生結構體,而是2個變量共用1個結構體
此時,2個變量,指向同1個結構體
refcount_gc 值爲 2 (若是沒有指針指引,會有垃圾回收機制清除)
cow寫時複製特性
$a = 3; $b = $a; /** * * 是否產生了2 個結構體? * 不是,共用1個, refcount_gc = 2; * */ $b = 5; echo $a, $b; // 3, 5 // $a,$b 指向同一個結構體,那麼,修改$b或$a,對方會不會受干擾 ? 沒有干擾到對方。具備寫時複製的特性
若是有一方修改,將會形成結構體的分裂
結構體一開始共用,到某一方要修改值時,才分裂。這種特性稱爲:COW 。Copy On Write。
引用賦值發生了什麼
當引用賦值時,雙方共用一個結構體(is_ref_gc=1)
關係圖例展現:
<?php // 強制分裂 $a = 3; /** * { * value: 3; * type: IS_LONG; * refcount_gc: 1; * is_ref_gc: 0; * } */ $b = $a; /** * { * value: 3; * type: IS_LONG; * refcount_gc: 2; * is_ref_gc: 0; * } */ $c = &$a; // 不會按照 底下結構體變化 /** * { * value: 3; * type: IS_LONG; * refcount_gc: 3; * is_ref_gc: 1; * } */ // 正確的結構體變化 // 若是is_ref_gc 0->1 的過程當中(從0到1,表示想引用變量)。refcount_gc>1。多個變量共享一個變量值。將會產生強制分裂 /** * // $a $c 結構體 * { * value: 3; * type: IS_LONG; * refcount_gc: 2; * is_ref_gc: 1; * } * * // $b 結構體 * { * value: 3; * type: IS_LONG; * refcount_gc: 1; * is_ref_gc: 0; * } * */ $c = 5; // a c /** * value: 5 * type: IS_LONG; * refcount_gc: 2; * is_ref_gc: 1; */ // b /** * value: 3 * type: IS_LONG; * refcount_gc: 1; * is_ref_gc: 0; */ echo $a, $b, $c; // 5 , 3 , 5
引用數組時的一些奇怪現象
// 引用數組時的怪現象 $arr = array(0, 1, 2, 3); $tmp = $arr; $arr[1] = 11; echo $tmp[1]; // 1 // 數組不會比較細緻的檢查,多維數組存在。 所以,判斷的時候,只會判斷外面 一層的 結構體。
數組不會比較細緻的檢查
// 先 引用 後 賦值 $arr = array(0, 1, 2, 3); $x = &$arr[1]; $tmp = $arr; $arr[1] = 999; echo $tmp[1]; // 999 . hash表中的zvalue結構體中會變成引用類型。 // 只去關注外面一層結構體,而不去關注 hash表中的值。 echo '<br/>'; // 先賦值,後引用 $arr = array(0, 1, 2, 3); $tmp = $arr; $x = &$arr[1]; $arr[1] = 999; echo $tmp[1]; // 1
循環數組時的怪現象
// 循環數組時的怪現象 $arr = array(0, 1, 2, 3); foreach ( $arr as $v ) { } var_dump(current($arr)); // 數組指針停留在數組結尾處, 取不到值. false echo '<br/>'; $arr = array(0, 1, 2, 3); foreach ( $arr as $val=>$key ) { // foreach 使用的 $arr 是 $arr的副本. $arr[$key] = $val; // 修改以後,就會產生分裂。 foreach 遍歷的是 $arr 的副本。 可是原數組的指針已經走了一步. } var_dump(current($arr)); // 1
$arr = array('a', 'b', 'c', 'd'); foreach ( $arr as &$val ) { // 該foreach 會致使 $val = &$arr[3]; } foreach ( $arr as $val ) { print_r($arr); echo '<br/>'; } // 兩個問題: // 數組使用時,要慎用引用。 // foreach 使用後,不會把數組的內部指針重置, 使用數組時,不要假想內部指針指向數組頭部. 也能夠在foreach 以後 reset(); 指針。
當執行到函數時,會生成函數的「執行環境結構體」,包含函數名,參數,執行步驟,所在的類(若是是方法),以及爲這個函數生成一個符號表。
符號表統一放在棧上,並把active_symbol_table指向剛產生的符號表。
// Zend/zend_compiles.h 文件中 // 源碼: struct _zend_execute_data { struct _zend_op *opline; zend_function_state function_state; zend_op_array *op_array; zval *object; 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; struct _zend_op *fast_ret; /* used by FAST_CALL/FAST_RET (finally keyword) */ zval *delayed_exception; call_slot *call_slots; call_slot *call; };
// 簡化: struct _zend_execute_data { ... zend_op_array *op_array; // 函數的執行步驟. 若是是函數調用。是函數調用的後的opcode HashTable *symbol_table; // 此函數的符號表地址 zend_class_entry *current_scope; // 執行當前做用域 zval * current_this; // 對象 調用 this綁定 zval * current_object; // object 的指向 ... }
一個函數調用屢次,會有多少個*op_array
?
一個函數產生 一個*op_array
. 調用屢次,會產生多個 環境結構體, 會依次入棧,而後順序執行。
調用多少次,就會入棧多少次。不一樣的執行環境,靠 惟一的 *op_array
來執行。
函數何時調用, 函數編譯後的 opcode 何時執行。
$age = 23; function t() { $age = 3; echo $age; } t(); /** * t 函數 在執行時,根據函數的參數,局部變量等,生成一個執行環境結構體。 * 結構體 入棧,函數編譯後的 opcode, 稱爲 op_array (就是執行邏輯)。開始執行, 以入棧的環境結構體爲環境來執行。 * 並生成此函數的 符號表, 函數尋找變量, 就在符號表中尋找。即局部變量。(一個環境結構體,就對應一張符號表) * * * 注意: 函數可能調用屢次。棧中可能有某函數的多個執行環境 入棧。可是 op_array 只有一個。 * */
靜態變量的實現
// Zend/zend_compile.h struct _zend_op_array { /* Common elements */ zend_uchar type; const 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; /* END of common elements */ zend_uint *refcount; zend_op *opcodes; zend_uint last; zend_compiled_variable *vars; int last_var; zend_uint T; zend_uint nested_calls; zend_uint used_stack; zend_brk_cont_element *brk_cont_array; int last_brk_cont; zend_try_catch_element *try_catch_array; int last_try_catch; zend_bool has_finally_block; /* static variables support */ HashTable *static_variables; zend_uint this_var; const char *filename; zend_uint line_start; zend_uint line_end; const char *doc_comment; zend_uint doc_comment_len; zend_uint early_binding; /* the linked list of delayed declarations */ zend_literal *literals; int last_literal; void **run_time_cache; int last_cache_slot; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; };
// 簡化 struct _zend_op_array { ... HashTable *static_variables; // 靜態變量 ... }
編譯後的 op_array 只有一份。 靜態變量並無存儲在符號表(symbol_table)中.而是存放在op_array中。
function t() { static $age = 1; return $age += 1; } echo t(); echo t(); echo t(); // 靜態變量 再也不和 執行的結構體, 也再也不和 入棧的符號表有關。
// Zend/zend_constants.h // 常量結構體 typedef struct _zend_constant { zval value; // 變量結構體 int flags; // 標誌,是否大小寫敏感等 char *name; // 常量名 uint name_len; // int module_number; // 模塊名 } zend_constant;
define函數固然是 調用zend_register_constant聲明的常量
具體以下:Zend/zend_builtin_functions.c
// 源碼:
ZEND_FUNCTION(define) { char *name; int name_len; zval *val; zval *val_free = NULL; zend_bool non_cs = 0; int case_sensitive = CONST_CS; zend_constant c; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|b", &name, &name_len, &val, &non_cs) == FAILURE) { return; } if(non_cs) { case_sensitive = 0; } /* class constant, check if there is name and make sure class is valid & exists */ if (zend_memnstr(name, "::", sizeof("::") - 1, name + name_len)) { zend_error(E_WARNING, "Class constants cannot be defined or redefined"); RETURN_FALSE; } repeat: switch (Z_TYPE_P(val)) { case IS_LONG: case IS_DOUBLE: case IS_STRING: case IS_BOOL: case IS_RESOURCE: case IS_NULL: break; case IS_OBJECT: if (!val_free) { if (Z_OBJ_HT_P(val)->get) { val_free = val = Z_OBJ_HT_P(val)->get(val TSRMLS_CC); goto repeat; } else if (Z_OBJ_HT_P(val)->cast_object) { ALLOC_INIT_ZVAL(val_free); if (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) == SUCCESS) { val = val_free; break; } } } /* no break */ default: zend_error(E_WARNING,"Constants may only evaluate to scalar values"); if (val_free) { zval_ptr_dtor(&val_free); } RETURN_FALSE; } c.value = *val; zval_copy_ctor(&c.value); if (val_free) { zval_ptr_dtor(&val_free); } c.flags = case_sensitive; /* non persistent */ c.name = str_strndup(name, name_len); if(c.name == NULL) { RETURN_FALSE; } c.name_len = name_len+1; c.module_number = PHP_USER_CONSTANT; if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) { RETURN_TRUE; } else { RETURN_FALSE; } }
// 關鍵代碼: c.value = *val; zval_copy_ctor(&c.value); if (val_free) { zval_ptr_dtor(&val_free); } c.flags = case_sensitive; /* 大小寫敏感 */ c.name = str_strndup(name, name_len); if(c.name == NULL) { RETURN_FALSE; } c.name_len = name_len+1; c.module_number = PHP_USER_CONSTANT; /* 用戶定義常量 */ if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) { RETURN_TRUE; } else { RETURN_FALSE; }
常量就一個符號(哈希)表. 都使用一個符號表。因此全局有效。
常量的生成
int zend_register_constant(zend_constant *c TSRMLS_DC) { ... ... zend_hash_add(EG(zend_constants), name, c->name_len, (vaid*)c,sizeof(zend_constant, NULL) == FAILURE); ... ... }
對象定義常量
class Dog { public $name = 'kitty'; public function __toString () { return $this->name; } } $dog = new Dog(); define('DOG', $dog); print_r(DOG); /** * define 值爲對象時,會把對象裝成標量來存儲,須要類有 __toString魔術方法 */
對象的底層實現
Zend/zend.h
struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint refcount__gc; zend_uchar type; /* active type */ zend_uchar is_ref__gc; }; // zvalue typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { char *val; int len; } str; HashTable *ht; /* hash table value */ zend_object_value obj; zend_ast *ast; } zvalue_value; // 在 zend.h 中 查看到 `zend_object_value obj;` 是以zend_object_value 定義. 在Zend/zend_types.h 文件中繼續查看 // Zend/zend_types.h
定義zend_object_value 結構體
typedef struct _zend_object_value { zend_object_handle handle; const zend_object_handlers *handlers; } zend_object_value;
經過new出來的對象,返回的是什麼。是zend_object_value
. 並非真正的對象,而是對象的指針。
返回的 handle再次指向對象。
每次new一個對象,對象就存入一張hash表中。(形象的稱之爲對象池)
對象存儲時的特色:
// 對象 class Dog { public $leg = 4; public $wei = 20; } $dog = new Dog(); // $dog 是一個對象麼? // 嚴格說,並非對象. /** * { * handle --指向--> [hash表 {leg: 4, wei: 20}] // hash表中存在 對象 * } */ $d2 = $dog; $d2->leg = 5; echo $dog->leg, '`', $d2->leg; // 5`5 // 對象並非 引用賦值. 主要緣由 zval 結構體 是再次指向一個hash表中的 對象池 $d2 = false; echo $dog->leg; // 5
內存管理與垃圾回收
PHP封裝了對系統內存的請求
不要直接使用malloc直接請求內存
PHP函數須要內存的時候,是經過emalloc,efree.
emalloc,efree向 mm_heap索要空間。
zend 中底層都離不開hash表。PHP中的HashTable太強大。
PHP 底層 全部的變量都是 放在 zend_mm_heap 中。 而後經過 各自的hash表來指向或跟蹤。
zend虛擬機的運行原理
PHP語法實現
Zend/zend_language_scanner.l Zend/zend_language_parser.y
OPcode編譯
Zend/zend.compile.c
執行引擎
Zend/zend_vm_* Zend/zend_execute.c
以apache模塊運行時的流程