歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

Php學(xué)習(xí)之資源類型的使用詳解

發(fā)布時(shí)間:2017-08-17 20:29  回復(fù):0  查看:2499   最后回復(fù):2017-08-17 20:29  
本文和大家分享的主要是php中資源類型的使用相關(guān)內(nèi)容,一起來看看吧,希望對(duì)大家 學(xué)習(xí)php 有所幫助。
  資源類型是一種特殊類型,它實(shí)際上可以保存任意的C指針,對(duì)PHP表現(xiàn)出一個(gè)資源對(duì)象的模樣,例如:PHP里fopen的返回值就是一個(gè)resource。
  我們可以利用資源類型,保存類型對(duì)象的指針,比如:一個(gè)FILE*文件描述符,或者僅僅是一個(gè)簡單的char *字符串,其意義是可以將我們希望傳遞的C語言內(nèi)存對(duì)象通過zval的形式包裝起來,以便C和PHP跨語言傳遞。
  資源類型是一個(gè)zval的底層數(shù)據(jù)類型,叫做zend_resource:
  struct _zend_resource {
  zend_refcounted_h gc;
  int               handle; // TODO: may be removed ???
  int               type;
  void             *ptr;
  };
  · gc:zval底層數(shù)據(jù)類型的第一個(gè)字段都是引用計(jì)數(shù)。
  · handle:唯一標(biāo)識(shí)一個(gè)資源對(duì)象,后面會(huì)講到其來源。
  · type:標(biāo)識(shí)資源對(duì)象的類型,每個(gè)資源對(duì)象都屬于一個(gè)資源類型。
  · ptr:任意的C指針,保存我們需要用到的東西。
  為了使用資源,我們必須要注冊(cè)資源類型,之后才能創(chuàng)建資源對(duì)象。因此,我在擴(kuò)展的啟動(dòng)回調(diào)函數(shù)里完成資源類型的注冊(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函數(shù)可以注冊(cè)一個(gè)資源類型,該函數(shù)的主要目的是定義資源對(duì)象的銷毀回調(diào)函數(shù),以及資源類型的可讀名字,具體看一下定義:
  /* 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;
  }
  該函數(shù)原理簡單,創(chuàng)建一個(gè)類型zend_rsrc_list_dtors_entry的結(jié)構(gòu)體,里面的list_dtor_ex保存了非持久化資源對(duì)象的銷毀回調(diào)函數(shù),plist_dtor_ex是持久化資源對(duì)象的銷毀函數(shù)(我們通常用不到持久化的資源對(duì)象)。
  最后,將這個(gè)資源類型對(duì)應(yīng)的zend_rsrc_list_dtors_entry對(duì)象append到哈希表list_destructors中,以便后續(xù)銷毀資源對(duì)象時(shí)可以來找到對(duì)應(yīng)的銷毀函數(shù),其數(shù)組下標(biāo)就唯一標(biāo)識(shí)了這個(gè)資源類型,返回給調(diào)用者。
  可見,所謂的注冊(cè)資源類型,就是在一個(gè)全局哈希表HashTable list_destructors中保存了該類型資源對(duì)象的銷毀回調(diào)函數(shù)。
  再回頭看看我注冊(cè)資源類型的代碼,其參數(shù)的具體實(shí)現(xiàn)如下:
  // 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);
  }
  我將注冊(cè)返回的資源類型ID保存在myext_string_resource_id中,資源類型的描述信息是”myext_string_resource”(當(dāng)你var_dump資源對(duì)象時(shí)會(huì)顯示給用戶),myext_string_resource_dtor是資源銷毀函數(shù),當(dāng)資源引用計(jì)數(shù)降低為0時(shí),該函數(shù)將被回調(diào)以便我們有機(jī)會(huì)釋放zend_resource.ptr關(guān)聯(lián)的內(nèi)存資源。
  這里我的resource類型就是保存一個(gè)普通C字符串,所以我在回調(diào)函數(shù)里free它的內(nèi)存即可。
  在注冊(cè)了這個(gè)資源類型后,我們進(jìn)入測試環(huán)節(jié),我新增了一個(gè)測試函數(shù):
  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);
  首先在堆上分配了一個(gè)C字符串,然后調(diào)用zend_register_resource創(chuàng)建一個(gè)zend_resource資源對(duì)象。函數(shù)的第1個(gè)參數(shù)是我們關(guān)聯(lián)的底層數(shù)據(jù)ptr,第2個(gè)參數(shù)是資源類型的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);
  }
  創(chuàng)建一個(gè)特定類型的zend_resource對(duì)象,其實(shí)就是創(chuàng)建一個(gè)zend_resource結(jié)構(gòu)并填充handle、ptr、type字段,之后追加到全局哈希表EG(regular_list)中即可。
  index是哈希表EG(regular_list)的下一個(gè)空閑整形下標(biāo),ptr是我們分配的C字符串,type是之前注冊(cè)的資源類型ID。通過ZVAL_NEW_RES宏可以創(chuàng)建一個(gè)zend_resource對(duì)象,并將這些信息賦值給zend_resource各個(gè)字段。最后,調(diào)用zend_hash_index_add_new即可將這個(gè)zend_resource資源對(duì)象保存到EG(regular_list)的index下標(biāo)中去。
  由此可見,所有的資源對(duì)象都順序排列在全局哈希表(PHP的array)EG(regular_list)中,因此它們默認(rèn)引用計(jì)數(shù)都是1。我們可以看一下EG(regular_list)這個(gè)哈希表的初始化過程:
  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可以傳入一個(gè)value的析構(gòu)函數(shù),這里是list_entry_destructor。當(dāng)從EG(regular_list)中刪除一個(gè)key時(shí),析構(gòu)函數(shù)將被調(diào)用。
  它首先取出zval的底層zend_resource,然后開始釋放這個(gè)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個(gè)資源對(duì)象,首先是去注冊(cè)資源類型的哈希表list_destructors中找到對(duì)應(yīng)的資源銷毀回調(diào)函數(shù),之后將zend_resource傳給銷毀函數(shù)進(jìn)行釋放。最后,會(huì)將zend_resource自身的內(nèi)存通過efree釋放。
  總結(jié)起來,刪除一個(gè)資源對(duì)象的的前提是其引用計(jì)數(shù)為0,刪除資源對(duì)象的過程就是先通過資源類型哈希表找到銷毀函數(shù),然后回調(diào)完成底層數(shù)據(jù)的銷毀,最后釋放資源對(duì)象自身內(nèi)存。
  一般創(chuàng)建了資源對(duì)象之后,我們最有可能將其返回給用戶,因此需要將zend_resource包裝到zval內(nèi)部,這一步記得增加額外的引用計(jì)數(shù):
  // wrappped with zval, refcount=2
  zval res_zval;
  ZVAL_RES(&res_zval, res);
  zval_addref_p(&res_zval);
  assert(GC_REFCOUNT(res) == 2);
  而顯式的釋放一個(gè)資源對(duì)象有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個(gè)引用計(jì)數(shù),如果引用計(jì)數(shù)降低為0,就執(zhí)行資源對(duì)象的刪除流程(上面已經(jīng)提到過了,只需要從EG(regular_list)中刪除它即可觸發(fā)后續(xù)一系列基于回調(diào)的銷毀流程):
  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;
  }
  }
  因?yàn)槲覀冊(cè)趜val res_val中額外保存了1個(gè)引用計(jì)數(shù),當(dāng)前資源對(duì)象尚未銷毀。下面,我們從zval中取出zend_resource對(duì)象的底層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可以從一個(gè)zval中的zend_resource對(duì)象中取出ptr,它只是額外做了一次資源類型的校驗(yàn)而已(如果類型不對(duì),還會(huì)拋出一個(gè)錯(cuò)誤信息):
  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,此時(shí)zend_resource的引用計(jì)數(shù)將降低為0:
  // release resource through zval, left refcount=0, zend_list_free is called
  zval_ptr_dtor(&res_zval);
  其背后的實(shí)現(xiàn):
  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是經(jīng)過zend_list_free函數(shù),它斷言當(dāng)前引用計(jì)數(shù)為0,并從EG(regular_list)中刪除該zend_resource觸發(fā)銷毀流程:
  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;
  }
  }


來源:魚兒的博客
您還未登錄,請(qǐng)先登錄

熱門帖子

最新帖子

?