Swift51.com
麦子学院 头像
麦子学院  2017-08-17 20:29

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

回复:0  查看:2520  
本文和大家分享的主要是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;
  }
  }


来源:鱼儿的博客