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
作爲索引的依據。