php底層分析

PHP編譯特點

編譯型語言

對於C語言,C++,編譯成機器碼(二進制)來運行。
Java語言,把.java 編譯成.class, 稱爲bytecode(字節碼),由jvm來運行

解釋型語言

解釋器解釋執行。 典型的如: linux shell

解釋器逐行來執行命令

PHP執行

PHP是先編譯後執行

PHP稍有特殊,雖然是一個腳本語言,但不是靠解釋器解釋。而是zend虛擬機執行,屏蔽了操作系統的區別。

PHP代碼編譯成 opcode,由zend虛擬機來執行opcode

但是opcode ,PHP腳本一結束,opcode就清除了。


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 $arr[1]; // 1// 數組不會比較細緻的檢查,多維數組存在。 因此,判斷的時候,只會判斷外面 一層的 結構體。


數組不會比較細緻的檢查

// 先 引用 後 賦值
$arr = array(0123);
$x = &$arr[1];
$tmp = $arr;
$arr[1] = 999;
echo $tmp[1]; // 999 . hash表中的zvalue結構體中會變成引用類型。
// 只去關注外面一層結構體,而不去關注 hash表中的值。
 
echo '<br/>';// 先賦值,後引用
$arr = array(0123);
$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));  // 數組指針停留在數組結尾處, 取不到值. falseecho '<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函數的實現

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_strndu
發佈了36 篇原創文章 · 獲贊 19 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章