溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PHP底層內核源碼之變量zend_zval結構體的示例分析

發(fā)布時間:2021-06-11 10:47:15 來源:億速云 閱讀:200 作者:小新 欄目:編程語言

小編給大家分享一下PHP底層內核源碼之變量zend_zval結構體的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

zend_string的 結構體 的源碼。

struct _zend_string {
zend_refcounted_h gc; //占用8個字節(jié) 用于gc的計數(shù)和字符串類型的記錄
zend_ulong        h;        // 占用8個字節(jié) 用于記錄 字符串的哈希值
size_t            len;       //占用8個字節(jié)    字符串的長度
char              val[1];   //占用1個字節(jié)    字符串的值存儲位置
};

其中 len 變量 使得 zend_string 具備了 二進制安全 的特性

gc 也就是zend_refcounted_h 結構體的加持 可以實現(xiàn) 寫時復制 (寫時拷貝 copy-on-write) 的功能

typedef struct _zend_refcounted_h {
uint32_t         refcount;//引用數(shù)
union {
uint32_t type_info;   //字符串所屬的變量類別
} u;
} zend_refcounted_h;

copy-on-write 技術在redis 和linux內核里廣泛應用

比如 Redis需要創(chuàng)建當前服務器進程的子進程,而大多數(shù)操作系統(tǒng)都采用寫時復制(copy-on-write)來優(yōu)化子進程的使用效率,所以在子進程存在期間,服務器會提高負載因子的閾值,從而避免在子進程存在期間進行哈希表擴展操作,避免不必要的內存寫入操作,最大限度地節(jié)約內存。

PHP 7也采用了寫時復制從而在進行賦值操作時比較節(jié)省內存,當字符串在賦值時并不直接拷貝一份數(shù)據(jù),而是把zend_string結構體里的 _zend_refcounted_h中的 refcount 做+1 運算,字符串銷毀時再把zend_string結構體里的 _zend_refcounted_h中的 refcount 做-1 運算。

如果您看過 陳雷大佬寫的 《PHP底層源碼設計與實現(xiàn)》 一書 可以會發(fā)現(xiàn) 稍微不一樣 因為 我的版本是PHP7.4 書中版本 與我本地安裝的不同 ,猜測可能是為了統(tǒng)一進行內存管理。

zend_string結構體里面的gc.u.flags字段,gc.u.flags總共有8位,每個類別占一位,可以重復打標簽,理論上最多打8種標簽。目前PHP 7源碼主要涉及以下幾種:1)對于臨時的普通字符串,flags字段被標識為0。2)對于內部字符串,用于存儲PHP代碼中的字面量、標識符等,flags字段被標識成IS_STR_PERSISTENT |IS_STR_INTERNED。3)對于PHP已知字符串,flags字段會被標識成IS_STR_PERSISTENT|IS_STR_INTERNED|IS_STR_PERMANENT。

--------摘自 《PHP底層源碼設計與實現(xiàn)》

在 PHP7.4源碼底層會給 變量進行分類 方便內存的管理 其依賴于 zend_zval結構體里的u1.v.type_flags字段

struct _zval_struct {
 197         zend_value        value;         //變量
 198         union {
 199                 struct {
 200                         ZEND_ENDIAN_LOHI_3(
 201                                 zend_uchar    type,  //變量類型           
 202                                 zend_uchar    type_flags,//可以用于變量的分類
 203                                 union {
 204                                         uint16_t  extra;        /* not further specified */
 205                                 } u)
 206                 } v;
 207                 uint32_t type_info;//變量類型
 208         } u1;
 209           u2;
 222 };

在555行有如下代碼

/* zval.u1.v.type_flags */
#define IS_TYPE_REFCOUNTED(1<<0) //REFCOUNTED 可以計數(shù)的
#define IS_TYPE_COLLECTABLE(1<<1) // TYPE_COLLECTABLE可收集的
#if 1
/* This optimized version assumes that we have a single "type_flag" */
/* IS_TYPE_COLLECTABLE may be used only with IS_TYPE_REFCOUNTED */
/*優(yōu)化后的版本假設我們有一個單一的"type_flag" */
/* IS_TYPE_COLLECTABLE只能與IS_TYPE_REFCOUNTED一起使用*/
# define Z_TYPE_INFO_REFCOUNTED(t)(((t) & Z_TYPE_FLAGS_MASK) != 0)
#else
# define Z_TYPE_INFO_REFCOUNTED(t)(((t) & (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT)) != 0)
#endif

所以PHP7.4版本中 zval.u1.v.type_flags 只有兩種類型 0或者 1 同時我也看了下最新的PHP8版本代碼 也是如此

為了更好的深入了解源碼 也將 前面兩節(jié)內容穿起來 我們安裝gdb 來調試下PHP

GDB(GNU symbolic debugger)簡單地說就是一個調試工具。它是一個受通用公共許可證即GPL保護的自由軟件。像所有的調試器一樣,GDB可以讓你調試一個程序,包括讓程序在你希望的地方停下,此時你可以查看變量、寄存器、內存及堆棧。更進一步你可以修改變量及內存值。GDB是一個功能很強大的調試器,它可以調試多種語言。在此我們僅涉及 C 和 C++ 的調試,而不包括其它語言。還有一點要說明的是,GDB是一個調試器,而不像 VC 是一個集成環(huán)境。你可以使用一些前端工具如XXGDB、DDD等。他們都有圖形化界面,因此使用更方便,但它們僅是GDB的一層外殼。因此,你仍應熟悉GDB命令。事實上,當你使用這些圖形化界面時間較長時,你才會發(fā)現(xiàn)熟悉GDB命令的重要性。

-----摘自oschina

[root@a3d3f47671d9 /]# php -v
PHP 7.4.15 (cli) (built: Feb 21 2021 09:07:07) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
[root@a3d3f47671d9 /]# gbv    
bash: gbv: command not found
[root@a3d3f47671d9 /]# gdb
bash: gdb: command not found
[root@a3d3f47671d9 /]# yum install gdb

.........

新建一個 PHP 文件

[root@a3d3f47671d9 cui]# vim php7-4-test-zval.php
 php7-4-test-zval.php                                                                              Buffers 
  <?php
   $a="abcdefg";
   echo $a;
   $b=88;
   echo $b;
   $c = $a;
   echo $c;
   echo $a;
   $c ="abc";
   echo $c;
   echo $a;

用 gdb 運行 PHP

[root@a3d3f47671d9 cui]# gdb php
GNU gdb (GDB) Red Hat Enterprise Linux 8.2-12.el8
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from php...done.
(gdb) b ZEND_ECHO_SPEC_CV_HANDLER   # b 命令意思是打斷點
Breakpoint 1 at 0x6dfe80: file /cui/php-7.4.15/Zend/zend_vm_execute.h, line 36987.
(gdb) r php7-4-test-zval.php
Starting program: /usr/local/bin/php php7-4-test-zval.php
warning: Error disabling address space randomization: Operation not permitted
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-127.el8.x86_64
warning: Loadable section ".note.gnu.property" outside of ELF segments
warning: Loadable section ".note.gnu.property" outside of ELF segments
warning: Loadable section ".note.gnu.property" outside of ELF segments
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
warning: Loadable section ".note.gnu.property" outside of ELF segments
warning: Loadable section ".note.gnu.property" outside of ELF segments
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /cui/php-7.4.15/Zend/zend_vm_execute.h:36987
36987SAVE_OPLINE();
Missing separate debuginfos, use: yum debuginfo-install libxcrypt-4.1.1-4.el8.x86_64 libxml2-2.9.7-8.el8.x86_64 sqlite-libs-3.26.0-11.el8.x86_64 xz-libs-5.2.4-3.el8.x86_64 zlib-1.2.11-16.el8_2.x86_64

可以看到 我的報錯了 因為我是在docker里跑的 centos鏡像 查了一些資料解決方法如下

編輯   /etc/yum.repos.d/CentOS-Debuginfo.repo 文件

修改enable=1

然后  yum install yum-utils

然后 dnf install glibc-langpack-en

yum debuginfo-install libxcrypt-4.1.1-4.el8.x86_64 libxml2-2.9.7-8.el8.x86_64 sqlite-libs-3.26.0-11.el8.x86_64 xz-libs-5.2.4-3.el8.x86_64 zlib-1.2.11-16.el8_2.x86_64

yum debuginfo-install glibc-2.28-127.el8.x86_64

讓我們再次運行一下 gdb

[root@a3d3f47671d9 cui]# vim php7-4-test-zval.php
[root@a3d3f47671d9 cui]# gdb php
GNU gdb (GDB) Red Hat Enterprise Linux 8.2-12.el8
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from php...done.
(gdb)

在gdb模式 命令b 可以設置斷點 你可以理解為PHP的 xdebug

還記得我們的 php7-4-test-zval.php 文件內容嗎

<?php
   $a="abcdefg";
   echo $a;
   $b=88;
   echo $b;
   $c = $a;
   echo $c;
   echo $a;
   $c ="abc";
   echo $c;
   echo $a;

這個echo 語言結構 是為了我們調試使用 這里是個小技巧

(ps 我這里說的語言結構 可沒說echo是函數(shù) 有一道面試題 php 中 echo()和var_dump()的主要區(qū)別?)

這個echo 其實是為了我們設置 斷點ZEND_ECHO_SPEC_CV_HANDLER

ZEND_ECHO_SPEC_CV_HANDLER其實是個宏 以后在詞法解析 語法分析 execute時候會詳細展開講解 如圖

PHP底層內核源碼之變量zend_zval結構體的示例分析

我們設置這個斷點的意義是為了讓程序在拼接echo 的時候暫停代碼 以便我們分析

(gdb) b ZEND_ECHO_SPEC_CV_HANDLER
Breakpoint 1 at 0x6dfe80: file /cui/php-7.4.15/Zend/zend_vm_execute.h, line 36987.

在gdb中 使用 r 運行文件

(gdb) r php7-4-test-zval.php 
Starting program: /usr/local/bin/php php7-4-test-zval.php
warning: Error disabling address space randomization: Operation not permitted
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /cui/php-7.4.15/Zend/zend_vm_execute.h:36987
36987SAVE_OPLINE();

在gdb中 用 n 可以執(zhí)行下一步操作

(gdb) n
36988z = EX_VAR(opline->op1.var);

這里我們暫且忽略繼續(xù)往下走

ZEND_ECHO_SPEC_CV_HANDLER的完整代碼如下(我貼出來只是想告訴你代碼里有這行代碼 讓你知道為什么往下走,你現(xiàn)階段不需要理解代碼,慢慢來 )

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ECHO_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *z;
SAVE_OPLINE();
//****************走到了此處**************
z = EX_VAR(opline->op1.var);
if (Z_TYPE_P(z) == IS_STRING) {
zend_string *str = Z_STR_P(z);
if (ZSTR_LEN(str) != 0) {
zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
}
} else {
zend_string *str = zval_get_string_func(z);
if (ZSTR_LEN(str) != 0) {
zend_write(ZSTR_VAL(str), ZSTR_LEN(str));
} else if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(z) == IS_UNDEF)) {
ZVAL_UNDEFINED_OP1();
}
zend_string_release_ex(str, 0);
}
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
(gdb) n
441return pz->u1.v.type;
(gdb) n
36991zend_string *str = Z_STR_P(z);

這里到了關鍵位置 變量z出現(xiàn)了

gdb中 用p 查看變量

(gdb) p z
$1 = (zval *) 0x7f4235a13070

這是一個 zval 結構體的指針地址

(gdb) p *z
$2 = {
  value = {lval = 139922344256128, dval = 6.9130823382525114e-310, counted = 0x7f4235a02280, 
    str = 0x7f4235a02280, arr = 0x7f4235a02280, obj = 0x7f4235a02280, res = 0x7f4235a02280, ref = 0x7f4235a02280, 
    ast = 0x7f4235a02280, zv = 0x7f4235a02280, ptr = 0x7f4235a02280, ce = 0x7f4235a02280, func = 0x7f4235a02280, 
    ww = {w1 = 899687040, w2 = 32578}},
  u1 = {v = {type = 6 '\006', type_flags = 0 '\000', u = {extra = 0}}, 
    type_info = 6}, 
  u2 = {next = 0, cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, 
    fe_iter_idx = 0, access_flags = 0, property_guard = 0, constant_flags = 0, extra = 0}}

看到這里應該很熟悉了 這就是源碼里的 結構體 格式

再次復習下 zval

struct _zval_struct {
          zend_value        value;         //變量
          union {
                 struct {
                         ZEND_ENDIAN_LOHI_3(
                                  zend_uchar    type,  //變量類型           
                                  zend_uchar    type_flags,//可以用于變量的分類
                                  union {
                                          uint16_t  extra;        /* not further specified */
                                  } u)
                  } v;
                  uint32_t type_info;//變量類型
          } u1;
            u2;
  };

gdb中變量$2 中 u1.v.type=6 我們拿出第二節(jié)的 類型定義源碼部分對比下

/* regular data types */
#define IS_UNDEF0
#define IS_NULL1
#define IS_FALSE2
#define IS_TRUE3
#define IS_LONG4
#define IS_DOUBLE5
#define IS_STRING6
#define IS_ARRAY7
#define IS_OBJECT8
#define IS_RESOURCE9
#define IS_REFERENCE10
.....
//其實有20種  剩下的不是常用類型 代碼就不全部粘出來了
u1.v.type=6 類型是 IS_STRING

再看下 zval種 value 對應的 zend_value聯(lián)合體中的代碼

ypedef union _zend_value {
zend_long         lval;/* long value */
double            dval;/* double value */
zend_refcounted  *counted;
zend_string      *str;
zend_array       *arr;
zend_object      *obj;
zend_resource    *res;
zend_reference   *ref;
zend_ast_ref     *ast;
zval             *zv;
void             *ptr;
zend_class_entry *ce;
zend_function    *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;

還記得聯(lián)合體的特性嗎 ? 所有值公用一個內存空間

上面的gdb中變量$2 的v.type=6 所以 在value中 值被str占用了 同時str 前面有個*

*星號 在C語言里代表指針 指向另外一個值的地址 所以指向 zend_string結構體

關于C語言指針您可以參考 菜鳥學院-指針

所以 接下來我們可以通過獲取value中的str來獲取 查看值

(gdb) p *z.value .str 
$4 = {gc = {refcount = 1, u = {type_info = 70}},
 h = 9223601495925209889, len = 7, val = "a"}

對比下 zend_string 源碼

struct _zend_string {
zend_refcounted_h gc;//引用計數(shù)
zend_ulong        h;                /* hash value */
size_t            len;//字符串長度
char              val[1];
};

* 你可能有疑問 val為啥 是val=“a” 我們不是定義$a="abcdefg"; 嗎 ? 還記得柔性數(shù)組嗎?:)

接下來繼續(xù)往下走

gdb中 用c 來執(zhí)行到下一個斷點處

(gdb) c
Continuing.
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /cui/php-7.4.15/Zend/zend_vm_execute.h:36987
36987SAVE_OPLINE();
(gdb) n
36988z = EX_VAR(opline->op1.var);
(gdb) n
441return pz->u1.v.type;
(gdb) n
36997zend_string *str = zval_get_string_func(z);
(gdb) p *z
$6 = {
  value = {lval = 88, dval = 4.3477776834029696e-322, counted = 0x58, str = 0x58, arr = 0x58, obj = 0x58, 
    res = 0x58, ref = 0x58, ast = 0x58, zv = 0x58, ptr = 0x58, ce = 0x58, func = 0x58, ww = {w1 = 88, w2 = 0}}, 
  u1 = {v = {type = 4 '\004', type_flags = 0 '\000', u = {extra = 0}}, type_info = 4}, 
  u2 = {next = 0, 
    cache_slot = 0, opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, 
    property_guard = 0, constant_flags = 0, extra = 0}}

u1.v.type=4 對應的是IS_LONG 代表整型 所以 在value中 值被lval占用了

可以看到值就是88 (lval不是指針 無需再跟進去查看了)

以上是“PHP底層內核源碼之變量zend_zval結構體的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

php
AI