PHP内核介绍及扩展开发指南
更新时间:2023-05-26 06:48:01 阅读量: 实用文档 文档下载
- php内核原理推荐度:
- 相关推荐
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
编写 PHP Extension
zhangdongjin@
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
目录
目录 ............................................................................................................................................ 2 一、 基础知识 ..................................................................................................................... 4
1.1 PHP变量的存储 .................................................................................................... 4
1.1.1 zval结构 ......................................................................................................... 4 1.1.2 引用计数 ........................................................................................................ 5 1.1.3 zval状态 ......................................................................................................... 5 1.1.4 zval状态切换 ................................................................................................. 6 1.1.5 参数传递 ........................................................................................................ 9 1.2 HashTable结构 ....................................................................................................... 9
1.2.1 数据结构 ...................................................................................................... 10 1.2.2 PHP数组 ...................................................................................................... 13 1.2.3 变量符号表 .................................................................................................. 14 1.3 内存和文件 ........................................................................................................... 15 二、 Extensions 的编写 ................................................................................................... 17
2.1 Hello World ........................................................................................................... 17
2.1.1 声明导出函数 .............................................................................................. 19 2.1.2 声明导出函数块 .......................................................................................... 20 2.1.3 填写模块信息 .............................................................................................. 21 2.1.4 实现导出函数 .............................................................................................. 23 2.2 使用参数 ............................................................................................................... 24
2.2.1 标准方法 ...................................................................................................... 25 2.2.2 底层方法 ...................................................................................................... 27 2.2.3 引用传递 ...................................................................................................... 29 2.2.4 编译检查(TODO) ........................................................................................ 30 2.3 返回值................................................................................................................... 30
2.3.1 返回引用 ...................................................................................................... 31 2.4 启动和终止函数 ................................................................................................... 33 2.5 调用PHP函数 ..................................................................................................... 34 2.6 访问PHP变量 ..................................................................................................... 37
2.6.1 设置 .............................................................................................................. 37 2.6.2 获取 .............................................................................................................. 38 2.6.3 常量 .............................................................................................................. 39 2.7 输出信息 ............................................................................................................... 42 三、 高级主题 ................................................................................................................... 43
3.1 使用数组 ............................................................................................................... 43
3.1.1 关联数组元素 .............................................................................................. 43 3.1.2 非关联数组元素 .......................................................................................... 44 3.2 使用资源 ............................................................................................................... 45
3.2.1 注册资源类型 .............................................................................................. 45 3.2.2 注册资源 ...................................................................................................... 46 3.2.3 获取资源 ...................................................................................................... 47 3.2.4 维护引用计数 .............................................................................................. 48
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
四、 类和对象(TODO) ..................................................................................................... 50 附录A. Extension的编译 .................................................................................................. 51 附录B. Extension的加载过程 .......................................................................................... 52
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
一、 基础知识
本章简要介绍一些Zend引擎的内部机制,这些知识和Extensions密切相关,同时也可以帮助我们写出更加高效的PHP代码。
1.1 PHP变量的存储
1.1.1 zval结构
Zend使用zval结构来存储PHP变量的值,该结构如下所示:
Zend根据type值来决定访问value的哪个成员,可用值如下:
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
根据这个表格可以发现两个有意思的地方:首先是PHP的数组其实就是一个HashTable,这就解释了为什么PHP能够支持关联数组了;其次,Resource就是一个long值,它里面存放的通常是个指针、一个内部数组的index或者其它什么只有创建者自己才知道的东西,可以将其视作一个handle。 1.1.2 引用计数
引用计数在垃圾收集、内存池以及字符串等地方应用广泛,Zend就实现了典型的引用计数。多个PHP变量可以通过引用计数机制来共享同一份zval,zval中剩余的两个成员is_ref和refcount就用来支持这种共享。
很明显,refcount用于计数,当增减引用时,这个值也相应的递增和递减,一旦减到零,Zend就会回收该zval。
那么is_ref呢? 1.1.3 zval状态
在PHP中,变量有两种——引用和非引用的,它们在Zend中都
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
是采用引用计数的方式存储的。对于非引用型变量,要求变量间互不相干,修改一个变量时,不能影响到其他变量,采用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.4 zval状态切换
当在一个zval上进行的所有赋值操作都是引用或者都是非引用时,一个is_ref就足够应付了。然而,世界总不会那么美好,PHP无法对用户进行这种限制,当我们混合使用引用和非引用赋值时,就必须要进行特别处理了。
情况I、看如下PHP代码:
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
这段代码首先进行了一次初始化,这将创建一个新的zval,is_ref=0, refcount=1,并将a指向这个zval;之后是两次非引用赋值,正如前面所说,只要把b和c都指向a的zval即可;最后一行是个引用赋值,需要is_ref为1,但是Zend发现c指向的zval并不是引用型的,于是为c创建单独的zval,并同时将d指向该zval。
从本质上来说,这也可以看作是一种Copy-On-Write,不仅仅是value,is_ref也是受保护的对象。
整个过程图示如下:
引用赋值前 引用赋值后
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
情况II,看如下PHP代码:
这段代码的前三句将把a、b和c指向一个zval,其is_ref=1, refcount=3;第四句是个非引用赋值,通常情况下只需要增加引用计数即可,然而目标zval属于引用变量,单纯的增加引用计数显然是错误的, Zend的解决办法是为d单独生成一份zval副本。
全过程如下所示:
新zval
非引用赋值前 非引用赋值后
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
1.1.5 参数传递
PHP函数参数的传递和变量赋值是一样的,非引用传递相当于非引用赋值,引用传递相当于引用赋值,并且也有可能会导致执行zval状态切换。这在后面还将提到。
1.2 HashTable结构
HashTable是Zend引擎中最重要、使用最广泛的数据结构,它被用来存储几乎所有的东西。
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
1.2.1 数据结构
HashTable数据结构定义如下:
总的来说,Zend的HashTable是一种链表散列,同时也为线性遍历进行了优化,图示如下:
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
arBuckets
HashTable里的两种数据结构
HashTable中包含两种数据结构,一个链表散列和一个双向链表,前者用于进行快速键-值查询,后者方便线性遍历和排序,一个Bucket同时存在于这两个数据结构中。
关于该数据结构的几点解释:
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
链表散列中为什么使用双向链表?
一般的链表散列只需要按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。
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
在HashTable的初始化函数里可以找到如下代码:
可见,Zend为一个Bucket分配了一块足够放下自己和key的内存,上半部分是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
值也不用指定,
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
只需要提供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代码都可以看见两个变量符号表——
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写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,它们的作
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
用和C中相应函数一样,不同的是这些函数从Zend自己的内存池中分配内存,并且它们可以实现基于页面的自动回收。在我们的模块中,为页面分配的内存应该使用这些API,而不是C例程,否则Zend会在页面结束时尝试efree掉我们的内存,其结果通常就是crush。
另外,Zend还提供了一组形如VCWD_xxx的宏用于替代C库和操作系统相应的文件API,这些宏能够支持PHP的虚拟工作目录,在模块代码中应该总是使用它们。宏的具体定义参见PHP源代码”TSRM/tsrm_virtual_cwd.h”。可能你会注意到,所有那些宏中并没有提供close操作,这是因为close的对象是已打开的资源,不涉及到文件路径,因此可以直接使用C或操作系统例程;同理,read/write之类的操作也是直接使用C或操作系统的例程。
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
二、 Extensions 的编写
理解了这些运行机制以后,本章着手介绍Extensions 的编写,但凡写程序的人都知道hello world,那好,就从hello world开始。
2.1 Hello World
这是摘自《PHP手册》的示例程序:
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
/* include standard header */ #include "php.h" /* declaration of functions to be exported */ ZEND_FUNCTION(first_module); /* compiled function list so Zend knows what's in this module */ zend_function_entry firstmod_functions[] = { ZEND_FE(first_module, NULL) {NULL, NULL, NULL} }; /* compiled module information */ zend_module_entry firstmod_module_entry = { STANDARD_MODULE_HEADER, "First Module", firstmod_functions, NULL, NULL, NULL, NULL, NULL, NO_VERSION_YET, STANDARD_MODULE_PROPERTIES }; /* implement standard "stub" routine to introduce ourselves to Zend */ #if COMPILE_DL_FIRST_MODULE ZEND_GET_MODULE(firstmod) #endif /* implement function that is meant to be made available to PHP */ ZEND_FUNCTION(first_module) { long parameter; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", ¶meter) == FAILURE) return; RETURN_LONG(parameter); }
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
这段代码实现了一个简单的extension,首先它包含了“php.h”,这是所有extensions都需要包含的头文件,它定义、声明了我们可以访问的所有Zend数据结构、常量和API等。下面对剩余的步骤进行解释。
2.1.1 声明导出函数
ZEND_FUNCTION宏用于声明一个可在PHP代码中调用的函数,其参数即成为PHP函数名,因此,这一句声明了一个名为first_module的PHP函数,将其展开如下:
可见,ZEND_FUNCTION就是简单的声明了一个名为zif_ first_module的C函数,zif可能是”Zend Internal Function”的缩写。函数的原型满足Zend引擎对PHP函数的调用约定,关于其参数将在后面章节进行解释。
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
2.1.2 声明导出函数块
声明C函数后,Zend并不知道如何调用,我们需要使用如下的语句来完成C函数到PHP函数的映射:
这创建了一个zend_function_entry数组,zend_function_entry存储了关于如何调用该PHP函数的信息,通过它Zend引擎就能够理解和调用我们的函数。
其定义如下:
fname是PHP函数名,是PHP代码能够通过它来调用我们的函数;handler是指向我们在前面声明的C函数的函数指针。这两个参数已经足以完成从C函数到PHP函数的映射。剩余的参数用于告诉Zend该PHP函数对于函数参数的要求,arg_info是个数组,它的每一项都描述了对应下标的参数,num_args
是参数的个数,具体将在
介绍PHP的一些内部机制,包括变量存储、数组实现等,随后详细介绍如何编写PHP扩展
后面的章节介绍。
我们可以手动填充一个zend_function_entry,但更好的办法是使用Zend提供的宏ZEND_FE,因为Zend并不保证这个结构以后不会变。ZEND_FE使用第一个参数作为PHP函数名,并且在添加了zif前缀后作为C函数名;第二个参数用于填充arg_info,通常使用NULL。上面的代码将得到这样一个zend_function_entry结构:{” first_module,”, zif_first_module, NULL, 0, 0}。当然,这并不是说PHP函数名必须和C函数名有什么关系,也可以通过宏ZEND_NAMED_FE来手动指定PHP函数名,不过这并不是个好主意。
我们必须为希望导出的每一个C函数都创建一个zend_function_entry结构,并将其放到一个数组中以备后用,数组最后一项的成员必须全部为NULL,这用于标记数组的结束。 2.1.3 填写模块信息
下一步需要将我们的模块介绍给Zend,主要包括我们的模块名和导出的函数,这通过填充一个zend_module_entry结构来完成。
正在阅读:
PHP内核介绍及扩展开发指南05-26
sz图元GIS解决方案09-05
小学生现场作文比赛题目集锦04-02
寻找什么作文800字06-14
《纳税筹划》2014电大试题及答案报过06-05
茶学专业本科生自我鉴定02-24
啤酒产糖化车间工艺流程设计12-01
《现代调味品技术及食品添加剂》实验讲义07-20
- 教学能力大赛决赛获奖-教学实施报告-(完整图文版)
- 互联网+数据中心行业分析报告
- 2017上海杨浦区高三一模数学试题及答案
- 招商部差旅接待管理制度(4-25)
- 学生游玩安全注意事项
- 学生信息管理系统(文档模板供参考)
- 叉车门架有限元分析及系统设计
- 2014帮助残疾人志愿者服务情况记录
- 叶绿体中色素的提取和分离实验
- 中国食物成分表2020年最新权威完整改进版
- 推动国土资源领域生态文明建设
- 给水管道冲洗和消毒记录
- 计算机软件专业自我评价
- 高中数学必修1-5知识点归纳
- 2018-2022年中国第五代移动通信技术(5G)产业深度分析及发展前景研究报告发展趋势(目录)
- 生产车间巡查制度
- 2018版中国光热发电行业深度研究报告目录
- (通用)2019年中考数学总复习 第一章 第四节 数的开方与二次根式课件
- 2017_2018学年高中语文第二单元第4课说数课件粤教版
- 上市新药Lumateperone(卢美哌隆)合成检索总结报告
- 内核
- 扩展
- 指南
- 开发
- 介绍
- PHP
- EPS聚苯板外墙保温施工方案
- 混凝土泵车安全操作规程技术交底
- 小学体育研修总结
- 欧姆龙PLC CP1E CP1H CP1L常见问题与解答
- 归国大学生回乡养羊创业
- 北京外墙保温隔热节能工程施工方案
- 总经理与各副总经理安全目标责任状
- Fe_CexZr0.9-xLa0.1O1.95-Al2O3整体式催化剂上的甲烷催化燃烧反应
- 北冶完小小学生综合素质评价表
- 【课本】二年级(下)第15讲 整数分拆综合
- 电控驱动防滑牵引力控制系统(ASRTRC)
- M520考勤机硬件说明书
- 费用核销新政策讲解
- 喝牛奶补钙 别忘多做运动 - 徐州日报社数字报纸
- 机关事业单位工作人员绩效考核量化标准
- 民政局机关内部管理制度
- 优秀教师个人主要事迹介绍
- 近年年国家公务员考试申论历年真题完整版及参考答案
- 军事理论(第二版)答案v1.0 by zmdyy03(1)
- Minitab区间估计和假设检验