PHP内核介绍与扩展开发指南-基础知识
本章简要介绍了Zend引擎内部的一些机制,以扩展密切相关,帮助我们写出更高效的PHP代码。
1.1 PHP变量存储
1.1.1 zval结构
Zend使用zval结构存储PHP变量的值,如下所示:
复制代码代码如下所示:
_zvalue_value { typedef联盟
长语句长值; / * * /
双双双值; / * * /
struct {
字符*;
Int len;
STR };
哈希表*哈希表值; / * * /
zend_object_value obj;
zvalue_value };
结构_zval_struct {
变量信息
zvalue_value值; / * * /
zend_uint引用计数;
zend_uchar型活跃型; / * * /
zend_uchar is_ref;
};
_zval_struct zval结构定义;
Zend决定价值的成员访问基于型值和有效值如下:
is_nulln /
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数组实际上是一个哈希表,这也解释了为什么PHP支持关联数组;其次,资源是一个长期的价值,它包含了通常是一个指针,指数或其他只有你知道事情的创造者,一个内部数组,这可以看成是一个手柄
1.1.1参考计数
引用计数被广泛用于垃圾收集,存储池,和字符串,和Zend实现了一个典型的引用计数,多个PHP变量可以通过引用计数机制共享相同的变量共享,和剩余的两个成员变量,is_ref和引用计数,用来支持这个共享。
很显然,引用计数用于计数,当参考加值的增加和减少,因此,一旦它被减少到零,Zend将恢复机制。
那么,is_ref
1.1.2 zval状态
在PHP中,有两类变量的引用和非引用,它们存储在Zend引用计数的方式。对于非引用类型的变量,变量需要对方,修改一个变量,不能影响其他变量,使用copy-on-write机制可以解决冲突时写一个Zend的变量,如果发现变量指向变量的多变量共享,是一份原始变量1变量引用计数,并减少引用计数,这个过程叫做zval。然而,对于引用变量,要求不同于非引用类型。分配给作业的变量必须被捆绑。修改一个变量修改所有捆绑变量。
很明显,需要指出的是,机制的当前状态两种情况分别处理。is_ref是目的。指出是否指向变量的引用或未分配的所有变量。变量是在这个时候,修改,和Zend将执行写时复制只有当其机制的is_ref为0,即非参考。
1.1.3 zval状态切换
当所有的赋值操作在zval引用或不引用,一个is_ref足以应付。然而,世界将不会那么好。PHP不能限制用户。当我们引用引用和非引用赋值时,我们必须进行特殊处理。
案例I,看看下面的PHP代码:
整个过程如下:
的第一个三字代码将A,B和C的一个机制,is_ref = 1,引用计数= 3;第四是非引用赋值,通常只需要增加引用计数。然而zval属于参考变量的目标,增加引用计数显然是错误的,解决的办法是Zend单独产生复制机制D.
整个过程如下:
1.1.1参数传递
PHP函数的传递和变量的赋值是相同的。非引用转换等同于非引用赋值。参考转让相当于引用赋值,也可能导致机制状态切换的执行。这将在后面讲到。
1.2哈希表结构
哈希表是Zend引擎的最重要和最广泛使用的数据结构,用于存储几乎一切。
1.1.1数据结构
哈希表的数据结构定义如下:
复制代码代码如下所示:
typedef struct斗{
页H / /存储散列;
单位nkeylength;
void * pData; / /指针的值,是一份用户数据
void * pdataptr;
结构斗* plistnext; / / plistnext和plistlast。
结构斗* plistlast; / /双链表,哈希表
结构斗* pnext;一个散列 / / pnext和塑料
结构斗*塑料; / /双链表
字符都可以实现{ 1 }; / /关键
桶};
_hashtable { typedef struct
单位ntablesize;
Uint nTableMask;
单位nnumofelements;
nnextfreeelement页;
斗* pinternalpointer用于元素遍历; / * * /
plisthead斗*;
plisttail斗*;
斗* arbuckets; / /哈希数组
dtor_func_t pdestructor; / /指定的哈希表的初始化,叫斗的破坏
Zend_bool persistent; / / memory allocation routines whether the use of C
unsigned char napplycount;
zend_bool bapplyprotection;
如果zend_debug #
int一致;
# endif
哈希表};
In general, Zend's HashTable is a chain hash and is also optimized for linear traversal, which is illustrated as follows:
哈希表包含两个数据结构,一个列表的哈希和双链表。前者用于快速键值查询,便于线性遍历和排序,在两种数据结构中都存在一个桶。
对数据结构的几种解释:
为什么在L链散列中使用双向链表
一般的哈希链只需要操作的关键,只有单链表是必要的。然而,Zend有时需要从链表中删除一个给定哈希桶,和一个双链表的使用可以非常有效的。
我ntablemask做什么
这个值是使用的哈希值转换为arbuckets数组下标。初始化哈希表时,Zend首先将ntablesize大小的内存arbuckets阵列,和ntablesize不小于最小的2 ^ n由用户指定,即二进制的10。ntablemask = ntablesize u2013 1,即二进制的01 *,当H ntablemask正好落在{ 0,1 } ntablesize u2013,Zend使用它作为访问arbuckets数组索引。
我pdataptr做什么
通常情况下,用户插入一个键值对的时候,Zend拷贝一份价值点的数据到一个值复制。复制操作需要调用Zend内部常规emalloc分配内存,这是一个非常耗时的操作和消耗比价值更大的内存。(如果更多的内存用于存储cookie),如果值很小,这将导致更大的浪费。考虑到HashTable主要是用来存储指针的值,或者引入pdataptr。当价值是只要一个指针,它直接pdataptr Zend的副本,和点数据来pdataptr。这避免了emalloc操作,也有助于提高缓存命中率。
为什么只有1的大小都可以实现不使用指针来管理关键
ArKey是关键的一个数组,但其规模仅为1,不足以放下的关键。下面的代码可以在初始化函数找到HashTable:
1P =(桶*)pemalloc(sizeof(桶)- 1 + nkeylength,HT ->持续);
可以看出,Zend拨斗,足以放下本身,关键的一段记忆,
L的上半部分是斗,下部是关键,并且都可以实现是斗的最后一个元素,所以你可以使用都可以实现访问的关键。这种技术在内存管理程序是最常见的,而分配内存,实际上是分配的内存比指定的大小,上部常被称为饼干,存储内存的信息,如块的大小,一个指针,指针。百度用这种方法传输程序。
没有指针用于管理密钥,减少emalloc操作,同时也增加了缓存的命中率。另一个重要的原因是,关键是固定的,并不会重新分配整个斗因为更长时间的关键。这也解释了为什么价值分配不在一起为一个阵列,因为价值变量。
1.2.2 PHP数组
还有一个问题关于哈希表,没有回答,这是什么nnextfreeelement呢
不同于一般的哈希,哈希表可以允许用户指定的哈希值,而忽略了重点,甚至没有指定密钥(nkeylength这时0)。同时,HashTable也支持附加操作。用户不需要指定散列值。他们只需要提供价值。在这个时候,Zend采用nnextfreeelement作为哈希,然后nnextfreeelement增加。
Hashtable这种行为看起来很奇怪,因为它不能够通过密钥访问的价值,它不是一个哈希完全,对问题的理解的关键是,PHP数组是使用哈希表-关联数组使用哈希表实现正常的元素为K-V映射,用户指定的键字符串;非联想数组作为数组索引的哈希值,关键是不存在的;与相关和非相关混合阵列时,或使用array_push操作,你需要使用nnextfreeelement。
再看价值。PHP数组的值直接使用机制的通用结构。数据是指zval *。根据上一节的介绍,zval *将直接存储在pdataptr。因为机制直接使用,该数组的元素可以是任意PHP类型。
阵列,即foreach遍历操作,每个等,通过双链表,哈希表做。PInternalPointer作为一个指针记录当前位置。
1.2.3变量符号表
除了阵列,HashTable也用来储存大量的其他数据,如PHP函数,变量符号,加载的模块、类成员,等等。
变量符号表等效于关联数组。它的关键是变量名。(可见,用一个很长的变量名是不是一个好主意),价值机制*。
在任何一个时间的PHP代码,可以看到两个变量:symbol_table和active_symbol_table:符号表用于存储全局变量,称为全局符号表;后者是一个指针变量的符号表的当前活动,通常是全局符号表。然而,当进入一个PHP函数的时候,它是指利用PHP代码的用户创建的功能。Zend将创造和点active_symbol_table的局部符号表函数的局部变量符号表。Zend总是用active_symbol_table访问变量,从而实现局部变量的范围控制。
然而,如果函数的本地访问标记为全局变量,可以将做特殊处理,创造symbol_table在active_symbol_table在相同名称的变量的引用,如果在symbol_table没有命名空间变量,它将创造第一。
1.3内存和文件
程序拥有的资源通常包括内存和文件。对于正常程序,这些资源是面向过程的。当过程完成时,操作系统或C库将自动回收我们未显式释放的资源。
然而,PHP程序有其特殊性。它是基于页面的。当页面运行时,它也将应用于内存或文件等资源。然而,当页面运行时,操作系统或C库可能不知道它所需要的资源回收。例如,我们PHP编译成模块和运行在Apache prefork模式Apache或工人。在这种情况下,Apache进程或线程是可重复使用,并通过PHP页面分配的内存将保留在记忆中直到核心。
为了解决这个问题,可以提供一组内存分配API,它的功能就像C的相应功能。不同的是,这些函数分配内存,从Zend自己的内存池,可以实现基于页面的自动恢复。在我们的模块,分配的内存页面应该使用这些API而不是C例程。否则,Zend将饱和在页末我们的记忆力下降,结果通常是粉碎。
Emalloc()
依芙利特()
Estrdup()
Estrndup()
Ecalloc()
Erealloc()
此外,Zend还提供了一组宏,如vcwd_xxx,取代C库和相应的API操作系统。这些宏可以支持PHP的虚拟目录,他们应该使用模块代码。该宏的具体定义称为PHP源码TSRM / tsrm_virtual_cwd.h.maybe你会发现所有的宏不提供关闭操作,这是因为接近的对象是开放的资源,对文件路径不相关,那么你可以直接使用C或读/写操作系统例程;同样,这样的操作也是直接使用C或操作系统例程。