PHP内核介绍及扩展开发指南—基础知识

简介:

一、 基础知识

本章简要介绍一些Zend引擎的内部机制,这些知识和Extensions密切相关,同时也可以帮助我们写出更加高效的PHP代码。

1.1 PHP变量的存储

1.1.1 zval结构

Zend使用zval结构来存储PHP变量的值,该结构如下所示:

 
 
  
  1. typedef union _zvalue_value {  
  2.     long lval;              /* long value */  
  3.     double dval;                /* double value */  
  4.     struct {  
  5.         char *val;  
  6.         int len;  
  7.     } str;  
  8.     HashTable *ht;              /* hash table value */  
  9.     zend_object_value obj;  
  10. } zvalue_value;  
  11.  
  12. struct _zval_struct {  
  13.     /* Variable information */  
  14.     zvalue_value value;     /* value */  
  15.     zend_uint refcount;  
  16.     zend_uchar type;            /* active type */  
  17.     zend_uchar is_ref;  
  18. };  
  19.  
  20. typedef struct _zval_struct zval;  
  21. <span id="more-597"></span>Zend根据type值来决定访问value的哪个成员,可用值如下: 
 

IS_NULL

N/A

IS_LONG

对应value.lval

IS_DOUBLE

对应value.dval

IS_STRING

对应value.str

IS_ARRAY

对应value.ht

IS_OBJECT

对应value.obj

IS_BOOL

对应value.lval.

IS_RESOURCE

对应value.lval

根据这个表格可以发现两个有意思的地方:首先是PHP的数组其实就是一个HashTable,这就解释了为什么PHP能够支持关联数组了;其次,Resource就是一个long值,它里面存放的通常是个指针、一个内部数组的index或者其它什么只有创建者自己才知道的东西,可以将其视作一个handle

1.1.1 引用计数

引用计数在垃圾收集、内存池以及字符串等地方应用广泛,Zend就实现了典型的引用计数。多个PHP变量可以通过引用计数机制来共享同一份zval,zval中剩余的两个成员is_ref和refcount就用来支持这种共享。

很明显,refcount用于计数,当增减引用时,这个值也相应的递增和递减,一旦减到零,Zend就会回收该zval。

那么is_ref呢?

1.1.2 zval状态

在PHP中,变量有两种——引用和非引用的,它们在Zend中都是采用引用计数的方式存储的。对于非引用型变量,要求变量间互不相干,修改一个变量时,不能影响到其他变量,采用Copy-On-Write机制即可解决这种冲突——当试图写入一个变量时,Zend若发现该变量指向的zval被多个变量共享,则为其复制一份refcount为1的zval,并递减原zval的refcount,这个过程称为“zval分离”。然而,对于引用型变量,其要求和非引用型相反,引用赋值的变量间必须是捆绑的,修改一个变量就修改了所有捆绑变量。

可见,有必要指出当前zval的状态,以分别应对这两种情况,is_ref就是这个目的,它指出了当前所有指向该zval的变量是否是采用引用赋值的——要么全是引用,要么全不是。此时再修改一个变量,只有当发现其zval的is_ref为0,即非引用时,Zend才会执行Copy-On-Write。

1.1.3 zval状态切换

当在一个zval上进行的所有赋值操作都是引用或者都是非引用时,一个is_ref就足够应付了。然而,世界总不会那么美好,PHP无法对用户进行这种限制,当我们混合使用引用和非引用赋值时,就必须要进行特别处理了。

情况I、看如下PHP代码:

 
  
  1. <!--p $a = 1;  $b = &$a;  $c = &$b;  $d = $c;  // 在一堆引用赋值中,插入一个非引用--> 
全过程如下所示:

这段代码的前三句将把a、b和c指向一个zval,其is_ref=1, refcount=3;第四句是个非引用赋值,通常情况下只需要增加引用计数即可,然而目标zval属于引用变量,单纯的增加引用计数显然是错误的, Zend的解决办法是为d单独生成一份zval副本。

全过程如下所示:

 

 

1.1.1 参数传递

PHP函数参数的传递和变量赋值是一样的,非引用传递相当于非引用赋值,引用传递相当于引用赋值,并且也有可能会导致执行zval状态切换。这在后面还将提到。

1.2 HashTable结构

HashTable是Zend引擎中最重要、使用最广泛的数据结构,它被用来存储几乎所有的东西。

1.1.1 数据结构

HashTable数据结构定义如下:

 
 
  
  1. typedef struct bucket {  
  2.     ulong h;                // 存放hash  
  3.     uint nKeyLength;  
  4.     void *pData;            // 指向value,是用户数据的副本  
  5.     void *pDataPtr;  
  6.     struct bucket *pListNext;   // pListNext和pListLast组成  
  7.     struct bucket *pListLast;   // 整个HashTable的双链表  
  8.     struct bucket *pNext;       // pNext和pLast用于组成某个hash对应  
  9.     struct bucket *pLast;       // 的双链表  
  10.     char arKey[1];              // key  
  11. } Bucket;  
  12.  
  13. typedef struct _hashtable {  
  14.     uint nTableSize;  
  15.     uint nTableMask;  
  16.     uint nNumOfElements;  
  17.     ulong nNextFreeElement;  
  18.     Bucket *pInternalPointer;   /* Used for element traversal */  
  19.     Bucket *pListHead;  
  20.     Bucket *pListTail;  
  21.     Bucket **arBuckets;         // hash数组  
  22.     dtor_func_t pDestructor;    // HashTable初始化时指定,销毁Bucket时调用  
  23.     zend_bool persistent;       // 是否采用C的内存分配例程  
  24.     unsigned char nApplyCount;  
  25.     zend_bool bApplyProtection;  
  26. #if ZEND_DEBUG  
  27.     int inconsistent;  
  28. #endif  
  29. } HashTable; 

总的来说,Zend的HashTable是一种链表散列,同时也为线性遍历进行了优化,图示如下:

 

HashTable中包含两种数据结构,一个链表散列和一个双向链表,前者用于进行快速键-值查询,后者方便线性遍历和排序,一个Bucket同时存在于这两个数据结构中。

关于该数据结构的几点解释:

链表散列中为什么使用双向链表?
一般的链表散列只需要按key进行操作,只需要单链表就够了。但是,Zend有时需要从链表散列中删除给定的Bucket,使用双链表可以非常高效的实现。

nTableMask是干什么的?
这个值用于hash值到arBuckets数组下标的转换。当初始化一个HashTable,Zend首先为arBuckets数组分配nTableSize大小的内存,nTableSize取不小于用户指定大小的最小的2^n,即二进制的10*。nTableMask = nTableSize – 1,即二进制的01*,此时h & nTableMask就恰好落在 [0, nTableSize – 1] 里,Zend就以其为index来访问arBuckets数组。

pDataPtr是干什么的?
通常情况下,当用户插入一个键值对时,Zend会将value复制一份,并将pData指向value副本。复制操作需要调用Zend内部例程 emalloc来分配内存,这是个非常耗时的操作,并且会消耗比value大的一块内存(多出的内存用于存放cookie),如果value很小的话,将会造成较大的浪费。考虑到HashTable多用于存放指针值,于是Zend引入pDataPtr,当value小到和指针一样长时,Zend就直接将其复制到pDataPtr里,并且将pData指向pDataPtr。这就避免了emalloc操作,同时也有利于提高Cache命中率。

arKey大小为什么只有1?为什么不使用指针管理key?
arKey是存放key的数组,但其大小却只有1,并不足以放下key。在HashTable的初始化函数里可以找到如下代码:

 
  
  1. p = (Bucket *) pemalloc(sizeof(Bucket) - 1 + nKeyLength, ht->persistent); 
可见,Zend为一个Bucket分配了一块足够放下自己和key的内存,

l  上半部分是Bucket,下半部分是key,而arKey“恰好”是Bucket的最后一个元素,于是就可以使用arKey来访问key了。这种手法在内存管理例程中最为常见,当分配内存时,实际上是分配了比指定大小要大的内存,多出的上半部分通常被称为cookie,它存储了这块内存的信息,比如块大小、上一块指针、下一块指针等,baidu的Transmit程序就使用了这种方法。
不用指针管理key,是为了减少一次emalloc操作,同时也可以提高Cache命中率。另一个必需的理由是,key绝大部分情况下是固定不变的,不会因为key变长了而导致重新分配整个Bucket。这同时也解释了为什么不把value也一起作为数组分配了——因为value是可变的。

1.2.2  PHP数组

关于HashTable还有一个疑问没有回答,就是nNextFreeElement是干什么的?

不同于一般的散列,Zend的HashTable允许用户直接指定hash值,而忽略key,甚至可以不指定key(此时,nKeyLength为0)。同时,HashTable也支持append操作,用户连hash值也不用指定,只需要提供value,此时,Zend就用nNextFreeElement作为hash,之后将nNextFreeElement递增。

HashTable的这种行为看起来很奇怪,因为这将无法按key访问value,已经完全不是个散列了。理解问题的关键在于,PHP数组就是使用HashTable实现的——关联数组使用正常的k-v映射将元素加入HashTable,其key为用户指定的字符串;非关联数组则直接使用数组下标作为hash值,不存在key;而当在一个数组中混合使用关联和非关联时,或者使用array_push操作时,就需要用nNextFreeElement了。

再来看value,PHP数组的value直接使用了zval这个通用结构,pData指向的是zval*,按照上一节的介绍,这个zval*将直接存储在pDataPtr里。由于直接使用了zval,数组的元素可以是任意PHP类型。

数组的遍历操作,即foreach、each等,是通过HashTable的双向链表来进行的,pInternalPointer作为游标记录了当前位置。

1.2.3  变量符号表

除了数组,HashTable还被用来存储许多其他数据,比如,PHP函数、变量符号、加载的模块、类成员等。

一个变量符号表就相当于一个关联数组,其key是变量名(可见,使用很长的变量名并不是个好主意),value是zval*。

在任一时刻PHP代码都可以看见两个变量符号表——symbol_table和active_symbol_table——前者用于存储全局变量,称为全局符号表;后者是个指针,指向当前活动的变量符号表,通常情况下就是全局符号表。但是,当每次进入一个PHP函数时(此处指的是用户使用PHP代码创建的函数),Zend都会创建函数局部的变量符号表,并将active_symbol_table指向局部符号表。Zend总是使用active_symbol_table来访问变量,这样就实现了局部变量的作用域控制。

但如果在函数局部访问标记为global的变量,Zend会进行特殊处理——在active_symbol_table中创建symbol_table中同名变量的引用,如果symbol_table中没有同名变量则会先创建。

1.3           内存和文件

程序拥有的资源一般包括内存和文件,对于通常的程序,这些资源是面向进程的,当进程结束后,操作系统或C库会自动回收那些我们没有显式释放的资源。

但是,PHP程序有其特殊性,它是基于页面的,一个页面运行时同样也会申请内存或文件这样的资源,然而当页面运行结束后,操作系统或C库也许不会知道需要进行资源回收。比如,我们将php作为模块编译到apache里,并且以prefork或worker模式运行apache。这种情况下apache进程或线程是复用的,php页面分配的内存将永驻内存直到出core。

为了解决这种问题,Zend提供了一套内存分配API,它们的作用和C中相应函数一样,不同的是这些函数从Zend自己的内存池中分配内存,并且它们可以实现基于页面的自动回收。在我们的模块中,为页面分配的内存应该使用这些API,而不是C例程,否则Zend会在页面结束时尝试efree掉我们的内存,其结果通常就是crush。

emalloc()
efree()
estrdup()
estrndup()
ecalloc()
erealloc()

另外,Zend还提供了一组形如VCWD_xxx的宏用于替代C库和操作系统相应的文件API,这些宏能够支持PHP的虚拟工作目录,在模块代码中应该总是使用它们。宏的具体定义参见PHP源代码”TSRM/tsrm_virtual_cwd.h”。可能你会注意到,所有那些宏中并没有提供close操作,这是因为close的对象是已打开的资源,不涉及到文件路径,因此可以直接使用C或操作系统例程;同理,read/write之类的操作也是直接使用C或操作系统的例程。

By zhangdongjin

 












本文转自百度技术51CTO博客,原文链接:http://blog.51cto.com/baidutech/743782,如需转载请自行联系原作者

相关文章
|
21天前
|
设计模式 算法 数据库连接
PHP中的设计模式:提高代码的可维护性与扩展性本文旨在探讨PHP中常见的设计模式及其应用,帮助开发者编写出更加灵活、可维护和易于扩展的代码。通过深入浅出的解释和实例演示,我们将了解如何使用设计模式解决实际开发中的问题,并提升代码质量。
在软件开发过程中,设计模式是一套经过验证的解决方案模板,用于处理常见的软件设计问题。PHP作为流行的服务器端脚本语言,也有其特定的设计模式应用。本文将重点介绍几种PHP中常用的设计模式,包括单例模式、工厂模式和策略模式,并通过实际代码示例展示它们的具体用法。同时,我们还将讨论如何在实际项目中合理选择和应用这些设计模式,以提升代码的可维护性和扩展性。
|
19天前
|
设计模式 存储 算法
PHP中的设计模式:策略模式的深入解析与应用在软件开发的浩瀚海洋中,PHP以其独特的魅力和强大的功能吸引了无数开发者。作为一门历史悠久且广泛应用的编程语言,PHP不仅拥有丰富的内置函数和扩展库,还支持面向对象编程(OOP),为开发者提供了灵活而强大的工具集。在PHP的众多特性中,设计模式的应用尤为引人注目,它们如同精雕细琢的宝石,镶嵌在代码的肌理之中,让程序更加优雅、高效且易于维护。今天,我们就来深入探讨PHP中使用频率颇高的一种设计模式——策略模式。
本文旨在深入探讨PHP中的策略模式,从定义到实现,再到应用场景,全面剖析其在PHP编程中的应用价值。策略模式作为一种行为型设计模式,允许在运行时根据不同情况选择不同的算法或行为,极大地提高了代码的灵活性和可维护性。通过实例分析,本文将展示如何在PHP项目中有效利用策略模式来解决实际问题,并提升代码质量。
|
2月前
|
安全 前端开发 PHP
PHP与现代Web开发:构建高效和可扩展的应用程序
【8月更文挑战第29天】在这篇文章中,我们将深入探讨PHP如何适应现代Web开发的需求。我们将通过实际案例分析,揭示PHP的核心优势,并展示如何利用这些优势来构建高性能、可扩展的Web应用。文章不仅提供理论知识,还包括具体的代码示例,旨在帮助开发者更好地理解和运用PHP解决实际问题。
|
2月前
|
SQL 关系型数据库 MySQL
PHP与数据库交互的艺术:深入探讨PDO扩展
【8月更文挑战第28天】在数字信息时代的海洋里,PHP作为一艘灵活的帆船,承载着无数网站和应用的梦想。而PDO扩展,则是这艘帆船上不可或缺的导航仪,指引着数据安全与效率的航向。本文将带你领略PHP与数据库交互的艺术,深入浅出地探索PDO的世界,从连接数据库到执行复杂的查询,每一步都清晰可见。我们将一起航行在这段奇妙的旅程上,解锁数据的奥秘,体验编程的乐趣。
22 1
|
2月前
|
存储 安全 Linux
【Azure 应用服务】App Service For Linux 怎么安装Composer,怎么安装PHP扩展,怎么来修改站点根路径启动程序?
【Azure 应用服务】App Service For Linux 怎么安装Composer,怎么安装PHP扩展,怎么来修改站点根路径启动程序?
|
3月前
|
运维 Serverless API
函数计算产品使用问题之如何使用PHP Runtime非内置扩展
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
4月前
|
Ubuntu 关系型数据库 MySQL
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
54 0
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
|
PHP
MAMP PRO 安装php swoole扩展
MAMP PRO 安装php swoole扩展
369 0
MAMP PRO 安装php swoole扩展
|
缓存 Linux PHP
17.17 LNMP安装php扩展模块
我们已经知道 LNMP 一键安装包默认只安装了最基本的 Nginx+Mysql+PHP 环境,并没有安装扩展功能模块,如果需要安装扩展模块该怎么办?
242 0
17.17 LNMP安装php扩展模块
|
Linux PHP
Linux环境下安装pecl工具,PHP扩展便捷安装
Linux环境下安装pecl工具,PHP扩展便捷安装
461 0