真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

PHP底層內(nèi)核源碼之變量zend_zval結(jié)構(gòu)體的示例分析

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

創(chuàng)新互聯(lián)為您提適合企業(yè)的網(wǎng)站設(shè)計(jì)?讓您的網(wǎng)站在搜索引擎具有高度排名,讓您的網(wǎng)站具備超強(qiáng)的網(wǎng)絡(luò)競爭力!結(jié)合企業(yè)自身,進(jìn)行網(wǎng)站設(shè)計(jì)及把握,最后結(jié)合企業(yè)文化和具體宗旨等,才能創(chuàng)作出一份性化解決方案。從網(wǎng)站策劃到網(wǎng)站制作、成都網(wǎng)站設(shè)計(jì), 我們的網(wǎng)頁設(shè)計(jì)師為您提供的解決方案。

zend_string的 結(jié)構(gòu)體 的源碼。

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

其中 len 變量 使得 zend_string 具備了 二進(jìn)制安全 的特性

gc 也就是zend_refcounted_h 結(jié)構(gòu)體的加持 可以實(shí)現(xiàn) 寫時(shí)復(fù)制 (寫時(shí)拷貝 copy-on-write) 的功能

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

copy-on-write 技術(shù)在redis 和linux內(nèi)核里廣泛應(yīng)用

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

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

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

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

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

在 PHP7.4源碼底層會(huì)給 變量進(jìn)行分類 方便內(nèi)存的管理 其依賴于 zend_zval結(jié)構(gòu)體里的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 可以計(jì)數(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)化后的版本假設(shè)我們有一個(gè)單一的"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 同時(shí)我也看了下最新的PHP8版本代碼 也是如此

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

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

.........

新建一個(gè) PHP 文件

[root@a3d3f47671d9 cui]# vim php7-4-test-zval.php
 php7-4-test-zval.php                                                                              Buffers 
  

用 gdb 運(yùn)行 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 
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:
.
Find the GDB manual and other documentation resources online at:
    .
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 命令意思是打斷點(diǎn)
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

可以看到 我的報(bào)錯(cuò)了 因?yàn)槲沂窃赿ocker里跑的 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

讓我們再次運(yùn)行一下 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 
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:
.
Find the GDB manual and other documentation resources online at:
    .
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from php...done.
(gdb)

在gdb模式 命令b 可以設(shè)置斷點(diǎn) 你可以理解為PHP的 xdebug

還記得我們的 php7-4-test-zval.php 文件內(nèi)容嗎

這個(gè)echo 語言結(jié)構(gòu) 是為了我們調(diào)試使用 這里是個(gè)小技巧

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

這個(gè)echo 其實(shí)是為了我們設(shè)置 斷點(diǎn)ZEND_ECHO_SPEC_CV_HANDLER

ZEND_ECHO_SPEC_CV_HANDLER其實(shí)是個(gè)宏 以后在詞法解析 語法分析 execute時(shí)候會(huì)詳細(xì)展開講解 如圖

PHP底層內(nèi)核源碼之變量zend_zval結(jié)構(gòu)體的示例分析

我們設(shè)置這個(gè)斷點(diǎn)的意義是為了讓程序在拼接echo 的時(shí)候暫停代碼 以便我們分析

(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 運(yùn)行文件

(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);

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

gdb中 用p 查看變量

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

這是一個(gè) zval 結(jié)構(gòu)體的指針地址

(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}}

看到這里應(yīng)該很熟悉了 這就是源碼里的 結(jié)構(gòu)體 格式

再次復(fù)習(xí)下 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
.....
//其實(shí)有20種  剩下的不是常用類型 代碼就不全部粘出來了
u1.v.type=6 類型是 IS_STRING

再看下 zval種 value 對應(yīng)的 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)合體的特性嗎 ? 所有值公用一個(gè)內(nèi)存空間

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

*星號(hào) 在C語言里代表指針 指向另外一個(gè)值的地址 所以指向 zend_string結(jié)構(gòu)體

關(guān)于C語言指針您可以參考 菜鳥學(xué)院-指針

所以 接下來我們可以通過獲取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;//引用計(jì)數(shù)
zend_ulong        h;                /* hash value */
size_t            len;//字符串長度
char              val[1];
};

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

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

gdb中 用c 來執(zhí)行到下一個(gè)斷點(diǎn)處

(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 對應(yīng)的是IS_LONG 代表整型 所以 在value中 值被lval占用了

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

以上是“PHP底層內(nèi)核源碼之變量zend_zval結(jié)構(gòu)體的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!


分享題目:PHP底層內(nèi)核源碼之變量zend_zval結(jié)構(gòu)體的示例分析
地址分享:http://weahome.cn/article/pephej.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部