摘要:也就是說該函數的返回值為指針。會將返回值的設為,設為。而這里的則是該函數的返回值,所以后面我們在解析該類型變量時,都需要將帶上。
在閱讀下面的內容之前,我們假定你已經對 PHP 7 基本的數據結構都有大致的了解了,這是下面內容閱讀的前提。
我們分為兩大塊:
首先實現一個自定義的文件打開、讀取、寫入、關閉的文件操作擴展;
然后分析各個操作背后的實現原理,其中某些部分的實現我會和 PHP 5.3 使用資源包裹第三方擴展源碼解讀 對比分析。
0 通過原型生成擴展骨架
首先進入到源碼目錄的ext目錄中,添加一個文件操作的原型文件
1 [root@localhost php-src-php-7.0.3]# cd ext/
2 [root@localhost ext]# vim tipi_file.proto
編輯原型為
1 resource file_open(string filename, string mode)
2 string file_read(resource filehandle, int size)
3 bool file_write(resource filehandle, string buffer)
4 bool file_close(resource filehandle)
5 [root@localhost ext]# ./ext_skel --extname=tipi_file --proto=./tipi_file.proto
這樣一個簡單的文件操作擴展的代碼骨架就生成了。
完整代碼 tipi_file.c(https://github.com/zhoumengkang/notes/blob/master/php-extension/php7.0/tipi_file/tipi_file.c),可以先有一個大致的了解,這樣后面閱讀時,思路可能會清晰很多。
1 擴展的實現1.1 注冊資源類型
1.1.1 注冊資源 API
1 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)
參數 解釋
ld 釋放該資源時調用的函數。
pld 釋放用于在不同請求中始終存在的永久資源的函數。
type_name 是一個具有描述性類型名稱的字符串。
module_number 為引擎內部使用,當我們調用這個函數時,我們只需要傳遞一個已經定義好的module_number變量。
該 API 返回一個資源類型 id,該id應當被作為全局變量保存在擴展里,以便在必要的時候傳遞給其他資源API。
1.1.2 添加資源釋放回調函數
1 static void tipi_file_dtor(zend_resource *rsrc TSRMLS_DC){ 2 FILE *fp = (FILE *) rsrc->ptr; 3 fclose(fp); 4 }
我們發現該函數的參數類型是zend_resource。這是 PHP7 新增的數據結構,在 PHP 5 則是zend_rsrc_list_entry。細節的內容,我們留在后面分析。
1.1.3 在PHP_MINIT_FUNCTION中注冊
我們知道在 PHP 生命周期中,當 PHP 被裝載時,PHP_MINIT_FUNCTION(模塊啟動函數)即被引擎調用。這使得引擎做一些例如資源類型,注冊INI變量等的一次初始化。
那么我們需要在這里通過zend_register_list_destructors_ex在PHP_MINIT_FUNCTION來注冊資源類型。
1 PHP_MINIT_FUNCTION(tipi_file) 2 { 3 /* If you have INI entries, uncomment these lines 4 REGISTER_INI_ENTRIES(); 5 */ 6 7 le_tipi_file = zend_register_list_destructors_ex(tipi_file_dtor, NULL, TIPI_FILE_TYPE, module_number); 8 return SUCCESS; 9 }
其中TIPI_FILE_TYPE在前面已經定義了,是該擴展的別名(具體可以對比著代碼 tipi_file.c 查看鏈接描述
1.2 注冊資源
1.2.1 注冊資源 API
在 PHP 7 中刪除了原來的ZEND_REGISTER_RESOURCE宏,直接使用zend_register_resource函數
1 ZEND_API zend_resource* zend_register_resource(void *rsrc_pointer, int rsrc_type)
參數 解釋
rsrc_pointer 資源數據指針
rsrc_type 注冊資源類型時獲得的資源類型 id
1.2.2 在 file_open函數中實現資源的注冊
1 PHP_FUNCTION(file_open) 2 { 3 char *filename = NULL; 4 char *mode = NULL; 5 int argc = ZEND_NUM_ARGS(); 6 size_t filename_len; 7 size_t mode_len; 8 9 if (zend_parse_parameters(argc TSRMLS_CC, "ss", &filename, &filename_len, &mode, &mode_len) == FAILURE) 10 return; 11 12 // 使用 VCWD 宏取代標準 C 文件操作函數 13 FILE *fp = VCWD_FOPEN(filename, mode); 14 15 if (fp == NULL) { 16 RETURN_FALSE; 17 } 18 19 RETURN_RES(zend_register_resource(fp, le_tipi_file)); 20 }
其中RETURN_RES宏的作用是將返回的zend_resource添加到zval中,然后將最后的zval作為返回值。也就是說該函數的返回值為zval指針。RETURN_RES(zend_register_resource(fp, le_tipi_file))會將返回值的value.res設為fp,u1.type_info設為IS_RESOURCE_EX。大家可以根據源碼非常直觀的了解到,這里不粘貼代碼詳細說明了。
1.3 使用資源
1.3.1 使用資源 API
1 ZEND_API void zend_fetch_resource(zend_resource res, const char *resource_type_name, int resource_type)
在 PHP 7 中刪除了原有的ZEND_FETCH_RESOURCE宏,直接使用函數zend_fetch_resource,而且解析方式也變得簡單了很多,想比 PHP 5 要高效很多,后面我們再通過圖片分析對比。
參數 含義
res 資源指針
resource_type_name 該類資源的字符串別名
resource_type 該類資源的類型 id
1.3.2 解析資源的實現
當我們要實現文件的讀取時,最終還是需要使用原生的fread函數,所以這里需要通過zend_fetch_resource將zend_resource解析成為該資源包裹的原始的FILE *的指針。
1 PHP_FUNCTION(file_read)
2 {
3 int argc = ZEND_NUM_ARGS();
4 int filehandle_id = -1;
5 zend_long size;
6 zval *filehandle = NULL;
7 FILE *fp = NULL;
8 char *result;
9 size_t bytes_read;
10
11 if (zend_parse_parameters(argc TSRMLS_CC, "rl", &filehandle, &size) == FAILURE)
12 return;
13
14 if ((fp = (FILE *)zend_fetch_resource(Z_RES_P(filehandle), TIPI_FILE_TYPE, le_tipi_file)) == NULL) {
15 RETURN_FALSE;
16 }
17
18 result = (char *) emalloc(size+1);
19 bytes_read = fread(result, 1, size, fp);
20 result[bytes_read] = "0";
21
22 RETURN_STRING(result, 0);
23
24 }
這里需要說明,腳本自動生成的擴展代碼中還是使用ZEND_FETCH_RESOURCE, 是個 BUG,因為自動生成的腳本(ext/skeleton/create_stubs)還沒更新。
與之類似的文件的寫入操作,也很類似,這里就復制代碼了,請查看完整的代碼 tipi_file.c(https://github.com/zhoumengkang/notes/blob/master/php-extension/php7.0/tipi_file/tipi_file.c)
1.4 資源的刪除
1.4.1 資源刪除 API
ZEND_API int zend_list_close(zend_resource *res)
傳入需要被刪除的資源即可。該 API 看似非常簡單,實際做了很多工作,后面原理分析細說。
1.4.2 資源刪除的實現
我們在函數file_close中需要調用資源刪除 API
1 PHP_FUNCTION(file_close) 2 { 3 int argc = ZEND_NUM_ARGS(); 4 int filehandle_id = -1; 5 zval *filehandle = NULL; 6 7 if (zend_parse_parameters(argc TSRMLS_CC, "r", &filehandle) == FAILURE) 8 return; 9 10 zend_list_close(Z_RES_P(filehandle)); 11 RETURN_TRUE; 12 }
1.5 編譯安裝以及測試
1.5.1 編譯安裝
通過上面的編碼,一個簡單的第三方的擴展就實現了。查看完整版鏈接描述
下面的一些命令配置請根據自己的環境而定(安裝的過程可以參考最基礎的擴展開發教程鏈接描述
1 [root@localhost tipi_file]# php7ize
2 Configuring for:
3 PHP Api Version: 20151012
4 Zend Module Api No: 20151012
5 Zend Extension Api No: 320151012
6 [root@localhost tipi_file]# ./configure --with-php-config=/usr/local/php7/bin/php-config
7 ...
8 [root@localhost tipi_file]# make
9 ...
10 [root@localhost tipi_file]# make install
11 ...
直接用 php 腳本測試,就不一個功能一個功能寫測試樣例了,修改tipi_file.php文件。
1 $fp = file_open("./CREDITS","r+");
2 var_dump($fp);
3 var_dump(file_read($fp,6));
4 var_dump(file_write($fp,"zhoumengakng"));
5 var_dump(file_close($fp));
然后通過命令行執行
1 php7 -d"extension=tipi_file.so" tipi_file.php
2 源碼分析2.1 注冊資源類型源碼
1 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) 2 { 3 zend_rsrc_list_dtors_entry *lde; 4 zval zv; 5 6 lde = malloc(sizeof(zend_rsrc_list_dtors_entry)); 7 lde->list_dtor_ex = ld; 8 lde->plist_dtor_ex = pld; 9 lde->module_number = module_number; 10 lde->resource_id = list_destructors.nNextFreeElement; 11 lde->type_name = type_name; 12 ZVAL_PTR(&zv, lde); 13 14 if (zend_hash_next_index_insert(&list_destructors, &zv) == NULL) { 15 return FAILURE; 16 } 17 return list_destructors.nNextFreeElement-1; 18 }
其中
1 ZVAL_PTR(&zv, lde);
等價于
1 zv.value.ptr = (lde); 2 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帶上。
整個的注冊步驟可以總結為下圖:
2.2 資源的注冊
1 ZEND_API zend_resource* zend_register_resource(void *rsrc_pointer, int rsrc_type) 2 { 3 zval *zv; 4 5 zv = zend_list_insert(rsrc_pointer, rsrc_type); 6 7 return Z_RES_P(zv); 8 }
該函數的功能則是將zend_list_insert返回的zval中的資源指針返回。Z_RES_P宏在Zend/zend_types.h中定義。
重點分析zend_list_insert
1 ZEND_API zval *zend_list_insert(void *ptr, int type) 2 { 3 int index; 4 zval zv; 5 6 index = zend_hash_next_free_element(&EG(regular_list)); 7 if (index == 0) { 8 index = 1; 9 } 10 ZVAL_NEW_RES(&zv, index, ptr, type); 11 return zend_hash_index_add_new(&EG(regular_list), index, &zv); 12 }
其中zend_hash_next_free_element宏,返回&EG(regular_list)表的nNextFreeElement,后面用來作為索引查詢的依據。
而ZVAL_NEW_RES宏是 PHP 7 新增的一套東西,把一個資源裝載到zval里去,因為PHP 7 中Bucket只能存zval了。
#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宏了,追蹤代碼發現其最后等價于調用的是
_zend_hash_index_add_or_update_i(&EG(regular_list), index, &zv, HASH_ADD | HASH_ADD_NEW ZEND_FILE_LINE_RELAY_CC)
關于HashTable的具體操作,這里暫不做細致的分析,后面多帶帶再多帶帶說
2.3 解析資源源碼分析
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; }
在上面的例子中我們是這樣解析的
(FILE *)zend_fetch_resource(Z_RES_P(filehandle), TIPI_FILE_TYPE, le_tipi_file)
2.4 刪除資源源碼分析
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函數。 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)中刪除
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做為索引的依據。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/30305.html
摘要:重點分析其中宏,返回表的,后面用來作為索引查詢的依據。中資源的解析比中解析簡單快捷很多,得益于其結構的改變。先從逆向通過其在中索引層層關聯,找到該類資源的釋放回調函數,然后對該資源執行釋放回調函數。 PHP 擴展開發的文章,我均已更新至《TIPI》本篇承接上篇 PHP7 使用資源包裹第三方擴展的實現 PHP7 使用資源包裹第三方擴展原理分析 注冊資源類型源碼 [c] ZEND_API ...
摘要:釋放用于在不同請求中始終存在的永久資源的函數。這是新增的數據結構,在則是。這使得引擎做一些例如資源類型,注冊變量等的一次初始化。也就是說該函數的返回值為指針。會將返回值的設為,設為。資源的刪除資源刪除傳入需要被刪除的資源即可。 PHP 擴展開發的文章,我均已更新至《TIPI》 在閱讀下面的內容之前,我們假定你已經對 PHP 7 基本的數據結構 都有大致的了解了,這是下面內容閱讀的前提。...
摘要:隨后,執行官給出一張當張三存款發生變化之時,此機構的運作時序圖的確,小機構靠人力運作,大機構才靠制度運轉。第一條語句創建觀察員第一條語句張三我們調用的時候,就創建了對象,對象的所有屬性都將被拷貝至一個克隆對象并將克隆對象轉變成可觀察的。 ================前言=================== 初衷:網上已有很多關于 MobX 源碼解讀的文章,但大多閱讀成本甚高。...
閱讀 2283·2021-10-09 09:41
閱讀 1746·2019-08-30 15:53
閱讀 989·2019-08-30 15:52
閱讀 3444·2019-08-30 11:26
閱讀 768·2019-08-29 16:09
閱讀 3422·2019-08-29 13:25
閱讀 2260·2019-08-26 16:45
閱讀 1932·2019-08-26 11:51