天天看点

PHP7 使用资源包裹第三方扩展的实现及其源码解读

我们分为两大块:

首先实现一个自定义的文件打开、读取、写入、关闭的文件操作扩展;

首先进入到源码目录的<code>ext</code>目录中,添加一个文件操作的原型文件

编辑原型为

生成骨架

这样一个简单的文件操作扩展的代码骨架就生成了。

参数

解释

ld

释放该资源时调用的函数。

pld

释放用于在不同请求中始终存在的永久资源的函数。

type_name

是一个具有描述性类型名称的字符串。

module_number

为引擎内部使用,当我们调用这个函数时,我们只需要传递一个已经定义好的<code>module_number</code>变量。

该 api 返回一个资源类型 id,该id应当被作为全局变量保存在扩展里,以便在必要的时候传递给其他资源api。

1.1.2 添加资源释放回调函数

我们发现该函数的参数类型是<code>zend_resource</code>。这是 php7 新增的数据结构,在 php 5 则是<code>zend_rsrc_list_entry</code>。细节的内容,我们留在后面分析。

我们知道在 php 生命周期中,当 php 被装载时,<code>php_minit_function</code>(模块启动函数)即被引擎调用。这使得引擎做一些例如资源类型,注册ini变量等的一次初始化。

那么我们需要在这里通过<code>zend_register_list_destructors_ex</code>在<code>php_minit_function</code>来注册资源类型。

在 php 7 中删除了原来的<code>zend_register_resource</code>宏,直接使用<code>zend_register_resource</code>函数

rsrc_pointer

资源数据指针

rsrc_type

注册资源类型时获得的资源类型 id

其中<code>return_res</code>宏的作用是将返回的<code>zend_resource</code>添加到<code>zval</code>中,然后将最后的<code>zval</code>作为返回值。也就是说该函数的返回值为<code>zval</code>指针。<code>return_res(zend_register_resource(fp, le_tipi_file))</code>会将返回值的<code>value.res</code>设为<code>fp</code>,<code>u1.type_info</code>设为<code>is_resource_ex</code>。大家可以根据源码非常直观的了解到,这里不粘贴代码详细说明了。

在 php 7 中删除了原有的<code>zend_fetch_resource</code>宏,直接使用函数<code>zend_fetch_resource</code>,而且解析方式也变得简单了很多,想比 php 5 要高效很多,后面我们再通过图片分析对比。

含义

res

资源指针

resource_type_name

该类资源的字符串别名

resource_type

该类资源的类型 id

当我们要实现文件的读取时,最终还是需要使用原生的<code>fread</code>函数,所以这里需要通过<code>zend_fetch_resource</code>将<code>zend_resource</code>解析成为该资源包裹的原始的<code>file *</code>的指针。

这里需要说明,脚本自动生成的扩展代码中还是使用<code>zend_fetch_resource</code>, 是个 bug,因为自动生成的脚本(<code>ext/skeleton/create_stubs</code>)还没更新。

传入需要被删除的资源即可。该 api 看似非常简单,实际做了很多工作,后面原理分析细说。

我们在函数<code>file_close</code>中需要调用资源删除 api

直接用 php 脚本测试,就不一个功能一个功能写测试样例了,修改<code>tipi_file.php</code>文件。

然后通过命令行执行

其中

展开后,等价于

<code>list_destructors</code>是一个全局静态<code>hashtable</code>,资源类型注册时,将一个<code>zval</code>结构体变量<code>zv</code>存放入<code>list_destructors</code>的<code>ardata</code>中,而<code>zv</code>的<code>value.ptr</code>却指向了<code>zend_rsrc_list_dtors_entry *lde</code>,<code>lde</code>中包含的该种资源释放函数指针、持久资源的释放函数指针,资源类型名称,该资源在 hashtable 中的索引依据 (<code>resource_id</code>)等。

而这里的<code>resource_id</code>则是该函数的返回值,所以后面我们在解析该类型变量时,都需要将<code>resource_id</code>带上。

整个的注册步骤可以总结为下图:

PHP7 使用资源包裹第三方扩展的实现及其源码解读

该函数的功能则是将<code>zend_list_insert</code>返回的<code>zval</code>中的资源指针返回。<code>z_res_p</code>宏在<code>zend/zend_types.h</code>中定义。

重点分析<code>zend_list_insert</code>

其中<code>zend_hash_next_free_element</code>宏,返回<code>&amp;eg(regular_list)</code>表的<code>nnextfreeelement</code>,后面用来作为索引查询的依据。

而<code>zval_new_res</code>宏是 php 7 新增的一套东西,把一个资源装载到<code>zval</code>里去,因为php 7 中<code>bucket</code>只能存<code>zval</code>了。

代码比较清晰,首先根据<code>h</code>,<code>p</code>,<code>t</code>新建了一个资源,然后一起存入了<code>z</code>这个zval的结构体。(最后两个宏前面刚刚讨论过了)

最后就是<code>zend_hash_index_add_new</code>宏了,追踪代码发现其最后等价于调用的是

关于<code>hashtable</code>的具体操作,这里暂不做细致的分析,后面单独再单独说。

PHP7 使用资源包裹第三方扩展的实现及其源码解读

在上面的例子中我们是这样解析的

首先通过<code>z_res_p</code>宏,获取<code>filehandle</code>这个<code>zval</code>变量中的<code>zend_resource</code>。然后<code>zend_fetch_resource</code>中只是对比了<code>zend_resource</code>的<code>type</code>与我们预想的资源类型是否一致,然后返回了<code>zend_resource</code>的<code>*ptr</code>,最后转换成<code>file *</code>指针。

php7 中资源的解析比 php5中解析简单快捷很多,得益于其 zval 结构的改变。

原来php5中则需要通过<code>eg(regular_list)</code>查找,如下图所示。

PHP7 使用资源包裹第三方扩展的实现及其源码解读

而现在 php7的解析则直接从<code>zval</code>里解析出<code>zend_resource</code>,如下图所示:

PHP7 使用资源包裹第三方扩展的实现及其源码解读

与php5 不同的地方,这里不是每次都进来将其引用计数减一操作,而是直接调用<code>zend_resource_dtor</code>函数。

如果引用计数已经等于0或者小于0了,那么才从<code>eg(regular_list)</code>中删除

原理图还是引用上面的注册资源类型、并注册资源的图:

PHP7 使用资源包裹第三方扩展的实现及其源码解读

先从<code>zend_resource</code>逆向通过其<code>type</code>在<code>list_destructors</code>中索引层层关联,找到该类资源的释放回调函数,然后对该资源执行释放回调函数。

而后面的从<code>eg(regular_list)</code>中删除,则是通过<code>res-&gt;handler</code>做为索引的依据。

上一篇: shell
下一篇: 云计算