PHP 進階之路 - PHP7 使用資源包裹第三方擴展原理分析

PHP 擴展開發的文章,我均已更新至《TIPI
本篇承接上篇 PHP7 使用資源包裹第三方擴展的實現php

PHP7 使用資源包裹第三方擴展原理分析

註冊資源類型源碼

[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_destructorsarData 中,而 zvvalue.ptr 卻指向了 zend_rsrc_list_dtors_entry *ldelde中包含的該種資源釋放函數指針、持久資源的釋放函數指針,資源類型名稱,該資源在 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_resourcetype與咱們預想的資源類型是否一致,而後返回了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逆向經過其typelist_destructors中索引層層關聯,找到該類資源的釋放回調函數,而後對該資源執行釋放回調函數。
然後面的從EG(regular_list)中刪除,則是經過res->handler作爲索引的依據。

相關文章
相關標籤/搜索