PHP 扩展开发的文章,我均已更新至《TIPI》
本篇承接上篇 PHP7 使用资源包裹第三方扩展的实现php
[c] ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, const char *type_name, int module_number) { zend_rsrc_list_dtors_entry *lde; zval zv; lde = malloc(sizeof(zend_rsrc_list_dtors_entry)); lde->list_dtor_ex = ld; lde->plist_dtor_ex = pld; lde->module_number = module_number; lde->resource_id = list_destructors.nNextFreeElement; lde->type_name = type_name; ZVAL_PTR(&zv, lde); if (zend_hash_next_index_insert(&list_destructors, &zv) == NULL) { return FAILURE; } return list_destructors.nNextFreeElement-1; }
其中segmentfault
[c] ZVAL_PTR(&zv, lde);
等价于数据结构
[c] zv.value.ptr = (lde); zv.u1.type_info = IS_PTR;
list_destructors
是一个全局静态 HashTable
,资源类型注册时,将一个 zval
结构体变量 zv
存放入 list_destructors
的 arData
中,而 zv
的 value.ptr
却指向了 zend_rsrc_list_dtors_entry *lde
,lde
中包含的该种资源释放函数指针、持久资源的释放函数指针,资源类型名称,该资源在 hashtable
中的索引依据 (resource_id
)等。
而这里的 resource_id
则是该函数的返回值,因此后面咱们在解析该类型变量时,都须要将 resource_id
带上。
整个的注册步骤能够总结为下图:函数
[c] ZEND_API zend_resource* zend_register_resource(void *rsrc_pointer, int rsrc_type) { zval *zv; zv = zend_list_insert(rsrc_pointer, rsrc_type); return Z_RES_P(zv); }
该函数的功能则是将 zend_list_insert
返回的 zval
中的资源指针返回。Z_RES_P
宏在 Zend/zend_types.h
中定义。
重点分析 zend_list_insert
源码分析
[c] ZEND_API zval *zend_list_insert(void *ptr, int type) { int index; zval zv; index = zend_hash_next_free_element(&EG(regular_list)); if (index == 0) { index = 1; } ZVAL_NEW_RES(&zv, index, ptr, type); return zend_hash_index_add_new(&EG(regular_list), index, &zv); }
其中zend_hash_next_free_element
宏,返回&EG(regular_list)
表的nNextFreeElement
,后面用来做为索引查询的依据。
而ZVAL_NEW_RES
宏是 PHP7 新增的一套东西,把一个资源装载到zval
里去,由于PHP7 中Bucket
只能存zval
了。fetch
[c] #define ZVAL_NEW_RES(z, h, p, t) do { \ zend_resource *_res = \ (zend_resource *) emalloc(sizeof(zend_resource)); \ zval *__z; \ GC_REFCOUNT(_res) = 1; \ GC_TYPE_INFO(_res) = IS_RESOURCE; \ _res->handle = (h); \ _res->type = (t); \ _res->ptr = (p); \ __z = (z); \ Z_RES_P(__z) = _res; \ Z_TYPE_INFO_P(__z) = IS_RESOURCE_EX; \ } while (0)
代码比较清晰,首先根据h
,p
,t
新建了一个资源,而后一块儿存入了z
这个zval的结构体。(最后两个宏前面刚刚讨论过了)
最后就是zend_hash_index_add_new
宏了,追踪代码发现其最后等价于调用的是spa
[c] _zend_hash_index_add_or_update_i(&EG(regular_list), index, &zv, HASH_ADD | HASH_ADD_NEW ZEND_FILE_LINE_RELAY_CC)
关于PHP7 HashTable
的具体操做,这里暂不作细致的分析,后期更新前面的数据结构的章节。注册的整个逻辑以下图:指针
[c] ZEND_API void *zend_fetch_resource(zend_resource *res, const char *resource_type_name, int resource_type) { if (resource_type == res->type) { return res->ptr; } if (resource_type_name) { const char *space; const char *class_name = get_active_class_name(&space); zend_error(E_WARNING, "%s%s%s(): supplied resource is not a valid %s resource", class_name, space, get_active_function_name(), resource_type_name); } return NULL; }
在上面的例子中咱们是这样解析的code
[c] (FILE *)zend_fetch_resource(Z_RES_P(filehandle), TIPI_FILE_TYPE, le_tipi_file)
首先经过Z_RES_P
宏,获取filehandle
这个zval
变量中的zend_resource
。而后zend_fetch_resource
中只是对比了zend_resource
的type
与咱们预想的资源类型是否一致,而后返回了zend_resource
的*ptr
,最后转换成FILE *
指针。
PHP7 中资源的解析比 PHP5中解析简单快捷不少,得益于其 zval 结构的改变。
原来PHP5中则须要经过EG(regular_list)
查找,以下图所示:blog
而如今 PHP7的解析则直接从zval
里解析出zend_resource
,以下图所示:
[c] ZEND_API int zend_list_close(zend_resource *res) { if (GC_REFCOUNT(res) <= 0) { return zend_list_free(res); } else if (res->type >= 0) { zend_resource_dtor(res); } return SUCCESS; }
与PHP5 不一样的地方,这里不是每次都进来将其引用计数减一操做,而是直接调用zend_resource_dtor
函数。
[c] static void zend_resource_dtor(zend_resource *res) { zend_rsrc_list_dtors_entry *ld; zend_resource r = *res; res->type = -1; res->ptr = NULL; ld = zend_hash_index_find_ptr(&list_destructors, r.type); if (ld) { if (ld->list_dtor_ex) { ld->list_dtor_ex(&r); } } else { zend_error(E_WARNING, "Unknown list entry type (%d)", r.type); } }
若是引用计数已经等于0或者小于0了,那么才从EG(regular_list)
中删除
[c] ZEND_API int zend_list_free(zend_resource *res) { if (GC_REFCOUNT(res) <= 0) { return zend_hash_index_del(&EG(regular_list), res->handle); } else { return SUCCESS; } }
原理图仍是引用上面的注册资源类型、并注册资源的图。
先从zend_resource
逆向经过其type
在list_destructors
中索引层层关联,找到该类资源的释放回调函数,而后对该资源执行释放回调函数。
然后面的从EG(regular_list)
中删除,则是经过res->handler
作为索引的依据。