PHP_底層分析

PHP編譯特色

編譯型語言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

clipboard.png

opcode 可否緩存

PHP自己不支持,可是apc,xcache等加速器,實現了這樣的效果。

clipboard.png

變量的底層實現

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 (若是沒有指針指引,會有垃圾回收機制清除)

clipboard.png

寫時複製

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。

clipboard.png

引用賦值

引用賦值發生了什麼

當引用賦值時,雙方共用一個結構體(is_ref_gc=1)

關係圖例展現:

clipboard.png

強制分裂

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

// 數組不會比較細緻的檢查,多維數組存在。 所以,判斷的時候,只會判斷外面 一層的 結構體。

clipboard.png

數組不會比較細緻的檢查

// 先 引用 後 賦值
$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 的指向
    ...
}

clipboard.png

一個函數調用屢次,會有多少個*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();

// 靜態變量 再也不和 執行的結構體, 也再也不和 入棧的符號表有關。

clipboard.png

常量

// 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_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表中。(形象的稱之爲對象池)

clipboard.png

對象存儲時的特色:

// 對象

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

clipboard.png

內存分層

內存管理與垃圾回收

PHP封裝了對系統內存的請求
不要直接使用malloc直接請求內存

clipboard.png

PHP函數須要內存的時候,是經過emalloc,efree.
emalloc,efree向 mm_heap索要空間。

zend 中底層都離不開hash表。PHP中的HashTable太強大。

clipboard.png

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模塊運行時的流程

clipboard.png

相關文章
相關標籤/搜索