小编给大家分享一下PHP底层内核源码之变量zend_zval结构体的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!zend_string的 结构体 的源码
小编给大家分享一下PHP底层内核源码之变量zend_zval结构体的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
zend_string的 结构体 的源码。
struct _zend_string {zend_refcounted_h GC; //占用8个字节 用于gc的计数和字符串类型的记录zend_ulong h; // 占用8个字节 用于记录 字符串的哈希值size_t len; //占用8个字节 字符串的长度char val[1]; //占用1个字节 字符串的值存储位置};
其中 len 变量 使得 zend_string 具备了 二进制安全 的特性
gc 也就是zend_refcounted_h 结构体的加持 可以实现 写时复制 (写时拷贝 copy-on-write) 的功能
typedef struct _zend_refcounted_h {uint32_t refcount;//引用数uNIOn {uint32_t type_info; //字符串所属的变量类别} u;} zend_refcounted_h;
copy-on-write 技术在redis 和linux内核里广泛应用
比如 Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)来优化子进程的使用效率,所以在子进程存在期间,服务器会提高负载因子的阈值,从而避免在子进程存在期间进行哈希表扩展操作,避免不必要的内存写入操作,最大限度地节约内存。
php 7也采用了写时复制从而在进行赋值操作时比较节省内存,当字符串在赋值时并不直接拷贝一份数据,而是把zend_string结构体里的 _zend_refcounted_h中的 refcount 做+1 运算,字符串销毁时再把zend_string结构体里的 _zend_refcounted_h中的 refcount 做-1 运算。
如果您看过 陈雷大佬写的 《PHP底层源码设计与实现》 一书 可以会发现 稍微不一样 因为 我的版本是PHP7.4 书中版本 与我本地安装的不同 ,猜测可能是为了统一进行内存管理。
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底层源码设计与实现》
在 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; 205 } u) 206 } v; 207 uint32_t type_info;//变量类型 208 } u1; 209 u2; 222 };
在555行有如下代码
#define IS_TYPE_REFCOUNTED(1<<0) //REFCOUNTED 可以计数的#define IS_TYPE_COLLECTABLE(1<<1) // TYPE_COLLECTABLE可收集的#if 1# 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版本代码 也是如此
为了更好的深入了解源码 也将 前面两节内容穿起来 我们安装gdb 来调试下PHP
GDB(GNU symbolic debugger)简单地说就是一个调试工具。它是一个受通用公共许可证即GPL保护的自由软件。像所有的调试器一样,GDB可以让你调试一个程序,包括让程序在你希望的地方停下,此时你可以查看变量、寄存器、内存及堆栈。更进一步你可以修改变量及内存值。GDB是一个功能很强大的调试器,它可以调试多种语言。在此我们仅涉及 C 和 c++ 的调试,而不包括其它语言。还有一点要说明的是,GDB是一个调试器,而不像 VC 是一个集成环境。你可以使用一些前端工具如XXGDB、DDD等。他们都有图形化界面,因此使用更方便,但它们仅是GDB的一层外壳。因此,你仍应熟悉GDB命令。事实上,当你使用这些图形化界面时间较长时,你才会发现熟悉GDB命令的重要性。
-----摘自oschina
[root@a3D3f47671d9 /]# php -vPHP 7.4.15 (cli) (built: Feb 21 2021 09:07:07) ( NTS )Copyright (c) The PHP GroupZend Engine v3.4.0, Copyright (c) Zend Technologies[root@a3d3f47671d9 /]# gbv bash: gbv: command not found[root@a3d3f47671d9 /]# gdbbash: 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 phpGNU gdb (GDB) Red Hat Enterprise Linux 8.2-12.el8Copyright (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.phpStarting program: /usr/local/bin/php php7-4-test-zval.phpwarning: Error disabling address space randomization: Operation not permittedMissing separate debuginfos, use: yum debuginfo-install glibc-2.28-127.el8.x86_64warning: Loadable section ".note.gnu.property" outside of ELF segmentswarning: Loadable section ".note.gnu.property" outside of ELF segmentswarning: 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 segmentswarning: Loadable section ".note.gnu.property" outside of ELF segmentsBreakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /cui/php-7.4.15/Zend/zend_vm_execute.h:3698736987SAVE_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 phpGNU gdb (GDB) Red Hat Enterprise Linux 8.2-12.el8Copyright (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是函数 有一道面试题 php 中 echo()和var_dump()的主要区别?)
这个echo 其实是为了我们设置 断点ZEND_ECHO_SPEC_CV_HANDLER
ZEND_ECHO_SPEC_CV_HANDLER其实是个宏 以后在词法解析 语法分析 execute时候会详细展开讲解 如图
我们设置这个断点的意义是为了让程序在拼接echo 的时候暂停代码 以便我们分析
(gdb) b ZEND_ECHO_SPEC_CV_HANDLERBreakpoint 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.phpwarning: 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:3698736987SAVE_OPLINE();
在gdb中 用 n 可以执行下一步操作
(gdb) n36988z = EX_VAR(opline->op1.var);
这里我们暂且忽略继续往下走
ZEND_ECHO_SPEC_CV_HANDLER的完整代码如下(我贴出来只是想告诉你代码里有这行代码 让你知道为什么往下走,你现阶段不需要理解代码,慢慢来 )
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ECHO_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS){USE_OPLINEzval *z;SAVE_OPLINE();/ } u) } v; uint32_t type_info;//变量类型 } u1; u2; };
gdb中变量$2 中 u1.v.type=6 我们拿出第二节的 类型定义源码部分对比下
#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联合体中的代码
ypedef union _zend_value {zend_long lval;double dval;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;
还记得联合体的特性吗 ? 所有值公用一个内存空间
上面的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;//引用计数zend_ulong h; size_t len;//字符串长度char val[1];};
* 你可能有疑问 val为啥 是val=“a” 我们不是定义$a="abcdefg"; 吗 ? 还记得柔性数组吗?:)
接下来继续往下走
gdb中 用c 来执行到下一个断点处
(gdb) cContinuing.Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /cui/php-7.4.15/Zend/zend_vm_execute.h:3698736987SAVE_OPLINE();(gdb) n36988z = EX_VAR(opline->op1.var);(gdb) n441return pz->u1.v.type;(gdb) n36997zend_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结构体的示例分析”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网PHP编程频道!
--结束END--
本文标题: PHP底层内核源码之变量zend_zval结构体的示例分析
本文链接: https://lsjlt.com/news/279910.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0