码迷,mamicode.com
首页 > Web开发 > 详细

Php学习之资源类型的使用详解

时间:2017-08-17 20:30:12      阅读:205      评论:0      收藏:0      [点我收藏+]

标签:

本文和大家分享的主要是php中资源类型的使用相关内容,一起来看看吧,希望对大家 学习php 有所帮助。 
资源类型是一种特殊类型,它实际上可以保存任意的C指针,对PHP表现出一个资源对象的模样,例如:PHP里fopen的返回值就是一个resource。 
我们可以利用资源类型,保存类型对象的指针,比如:一个FILE*文件描述符,或者仅仅是一个简单的char *字符串,其意义是可以将我们希望传递的C语言内存对象通过zval的形式包装起来,以便C和PHP跨语言传递。 
资源类型是一个zval的底层数据类型,叫做zend_resource: 
struct _zend_resource { 
zend_refcounted_h gc; 
int               handle; // TODO: may be removed ??? 
int               type; 
void             *ptr; 
}; 
· gc:zval底层数据类型的第一个字段都是引用计数。 
· handle:唯一标识一个资源对象,后面会讲到其来源。 
· type:标识资源对象的类型,每个资源对象都属于一个资源类型。 
· ptr:任意的C指针,保存我们需要用到的东西。 
为了使用资源,我们必须要注册资源类型,之后才能创建资源对象。因此,我在扩展的启动回调函数里完成资源类型的注册: 
int extension_startup(int type, int module_number) { 
.... 
// register resource type 
myext_string_resource_id = zend_register_list_destructors_ex(myext_string_resource_dtor, NULL, MYEXT_STRING_RESOURCE_DTOR, module_number); 
assert(myext_string_resource_id != FAILURE); 
通过zend_register_list_destructors_ex函数可以注册一个资源类型,该函数的主要目的是定义资源对象的销毁回调函数,以及资源类型的可读名字,具体看一下定义: 
/* true global */static HashTable list_destructors; 
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; 
} 
该函数原理简单,创建一个类型zend_rsrc_list_dtors_entry的结构体,里面的list_dtor_ex保存了非持久化资源对象的销毁回调函数,plist_dtor_ex是持久化资源对象的销毁函数(我们通常用不到持久化的资源对象)。 
最后,将这个资源类型对应的zend_rsrc_list_dtors_entry对象append到哈希表list_destructors中,以便后续销毁资源对象时可以来找到对应的销毁函数,其数组下标就唯一标识了这个资源类型,返回给调用者。 
可见,所谓的注册资源类型,就是在一个全局哈希表HashTable list_destructors中保存了该类型资源对象的销毁回调函数。 
再回头看看我注册资源类型的代码,其参数的具体实现如下: 
// resource idint myext_string_resource_id = 0; 
// resource type description#define MYEXT_STRING_RESOURCE_DTOR "myext_string_resource" 
// resource destructor callbackvoid myext_string_resource_dtor(zend_resource *res) { 
assert(res->type == myext_string_resource_id); 
free(res->ptr); 
} 
我将注册返回的资源类型ID保存在myext_string_resource_id中,资源类型的描述信息是”myext_string_resource”(当你var_dump资源对象时会显示给用户),myext_string_resource_dtor是资源销毁函数,当资源引用计数降低为0时,该函数将被回调以便我们有机会释放zend_resource.ptr关联的内存资源。 
这里我的resource类型就是保存一个普通C字符串,所以我在回调函数里free它的内存即可。 
在注册了这个资源类型后,我们进入测试环节,我新增了一个测试函数: 
void zif_myext_test_resource(zend_execute_data *execute_data, zval *return_value) { 
char *string = strdup("i am a string resource"); 
zend_resource *res = zend_register_resource(string, myext_string_resource_id); 
assert(GC_REFCOUNT(res) == 1); 
首先在堆上分配了一个C字符串,然后调用zend_register_resource创建一个zend_resource资源对象。函数的第1个参数是我们关联的底层数据ptr,第2个参数是资源类型的ID: 
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_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_resource对象,其实就是创建一个zend_resource结构并填充handle、ptr、type字段,之后追加到全局哈希表EG(regular_list)中即可。 
index是哈希表EG(regular_list)的下一个空闲整形下标,ptr是我们分配的C字符串,type是之前注册的资源类型ID。通过ZVAL_NEW_RES宏可以创建一个zend_resource对象,并将这些信息赋值给zend_resource各个字段。最后,调用zend_hash_index_add_new即可将这个zend_resource资源对象保存到EG(regular_list)的index下标中去。 
由此可见,所有的资源对象都顺序排列在全局哈希表(PHP的array)EG(regular_list)中,因此它们默认引用计数都是1。我们可以看一下EG(regular_list)这个哈希表的初始化过程: 
void list_entry_destructor(zval *zv) 
{ 
zend_resource *res = Z_RES_P(zv); 
ZVAL_UNDEF(zv); 
if (res->type >= 0) { 
zend_resource_dtor(res); 
} 
efree_size(res, sizeof(zend_resource)); 
} 
int zend_init_rsrc_list(void) 
{ 
zend_hash_init(&EG(regular_list), 8, NULL, list_entry_destructor, 0); 
return SUCCESS; 
} 
我们知道zend_hash_init可以传入一个value的析构函数,这里是list_entry_destructor。当从EG(regular_list)中删除一个key时,析构函数将被调用。 
它首先取出zval的底层zend_resource,然后开始释放这个zend_resource的资源: 
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); 
} 
} 
释放1个资源对象,首先是去注册资源类型的哈希表list_destructors中找到对应的资源销毁回调函数,之后将zend_resource传给销毁函数进行释放。最后,会将zend_resource自身的内存通过efree释放。 
总结起来,删除一个资源对象的的前提是其引用计数为0,删除资源对象的过程就是先通过资源类型哈希表找到销毁函数,然后回调完成底层数据的销毁,最后释放资源对象自身内存。 
一般创建了资源对象之后,我们最有可能将其返回给用户,因此需要将zend_resource包装到zval内部,这一步记得增加额外的引用计数: 
// wrappped with zval, refcount=2 
zval res_zval; 
ZVAL_RES(&res_zval, res); 
zval_addref_p(&res_zval); 
assert(GC_REFCOUNT(res) == 2); 
而显式的释放一个资源对象有2种方法,第1种是直接操作zend_resource自身,其用法如下: 
// release resource directly, left refcount=1 
zend_list_delete(res); 
assert(GC_REFCOUNT(res) == 1); 
zend_list_delete类似于zend_string_release,它首先释放1个引用计数,如果引用计数降低为0,就执行资源对象的删除流程(上面已经提到过了,只需要从EG(regular_list)中删除它即可触发后续一系列基于回调的销毁流程): 
ZEND_API int zend_list_delete(zend_resource *res) 
{ 
if (--GC_REFCOUNT(res) <= 0) { 
return zend_hash_index_del(&EG(regular_list), res->handle); 
} else { 
return SUCCESS; 
} 
} 
因为我们在zval res_val中额外保存了1个引用计数,当前资源对象尚未销毁。下面,我们从zval中取出zend_resource对象的底层ptr: 
// validate and get resource ptr 
char *s = zend_fetch_resource_ex(&res_zval, MYEXT_STRING_RESOURCE_DTOR, myext_string_resource_id); 
assert(strcmp(s, "i am a string resource") == 0); 
zend_fetch_resource_ex可以从一个zval中的zend_resource对象中取出ptr,它只是额外做了一次资源类型的校验而已(如果类型不对,还会抛出一个错误信息): 
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; 
} 
ZEND_API void *zend_fetch_resource_ex(zval *res, const char *resource_type_name, int resource_type){
const char *space, *class_name; 
if (res == NULL) { 
if (resource_type_name) { 
class_name = get_active_class_name(&space); 
zend_error(E_WARNING, "%s%s%s(): no %s resource supplied", class_name, space, get_active_function_name(), resource_type_name); 
} 
return NULL; 
} 
if (Z_TYPE_P(res) != IS_RESOURCE) { 
if (resource_type_name) { 
class_name = get_active_class_name(&space); 
zend_error(E_WARNING, "%s%s%s(): supplied argument is not a valid %s resource", class_name, space, get_active_function_name(), resource_type_name); 
} 
return NULL; 
} 
return zend_fetch_resource(Z_RES_P(res), resource_type_name, resource_type); 
} 
最后,我们释放zval,此时zend_resource的引用计数将降低为0: 
// release resource through zval, left refcount=0, zend_list_free is called 
zval_ptr_dtor(&res_zval); 
其背后的实现: 
ZEND_API void ZEND_FASTCALL _zval_dtor_func(zend_refcounted *p ZEND_FILE_LINE_DC){ 
switch (GC_TYPE(p)) { 
..... 
case IS_RESOURCE: { 
zend_resource *res = (zend_resource*)p; 
/* destroy resource */ 
zend_list_free(res); 
break; 
} 
可见,最终释放zend_resource是经过zend_list_free函数,它断言当前引用计数为0,并从EG(regular_list)中删除该zend_resource触发销毁流程: 
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; 
} 
} 


来源:鱼儿的博客

Php学习之资源类型的使用详解

标签:

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
wjunxi
加入时间:2017-06-15
  关注此人  发短消息
文章分类
wjunxi”关注的人------(0
wjunxi”的粉丝们------(0
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!