摘要:也就是說該函數(shù)的返回值為指針。會將返回值的設(shè)為,設(shè)為。而這里的則是該函數(shù)的返回值,所以后面我們在解析該類型變量時,都需要將帶上。
在閱讀下面的內(nèi)容之前,我們假定你已經(jīng)對 PHP 7 基本的數(shù)據(jù)結(jié)構(gòu)都有大致的了解了,這是下面內(nèi)容閱讀的前提。
我們分為兩大塊:
首先實(shí)現(xiàn)一個自定義的文件打開、讀取、寫入、關(guān)閉的文件操作擴(kuò)展;
然后分析各個操作背后的實(shí)現(xiàn)原理,其中某些部分的實(shí)現(xiàn)我會和 PHP 5.3 使用資源包裹第三方擴(kuò)展源碼解讀 對比分析。
0 通過原型生成擴(kuò)展骨架
首先進(jìn)入到源碼目錄的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
這樣一個簡單的文件操作擴(kuò)展的代碼骨架就生成了。
完整代碼 tipi_file.c(https://github.com/zhoumengkang/notes/blob/master/php-extension/php7.0/tipi_file/tipi_file.c),可以先有一個大致的了解,這樣后面閱讀時,思路可能會清晰很多。
1 擴(kuò)展的實(shí)現(xiàn)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)
參數(shù) 解釋
ld 釋放該資源時調(diào)用的函數(shù)。
pld 釋放用于在不同請求中始終存在的永久資源的函數(shù)。
type_name 是一個具有描述性類型名稱的字符串。
module_number 為引擎內(nèi)部使用,當(dāng)我們調(diào)用這個函數(shù)時,我們只需要傳遞一個已經(jīng)定義好的module_number變量。
該 API 返回一個資源類型 id,該id應(yīng)當(dāng)被作為全局變量保存在擴(kuò)展里,以便在必要的時候傳遞給其他資源API。
1.1.2 添加資源釋放回調(diào)函數(shù)
1 static void tipi_file_dtor(zend_resource *rsrc TSRMLS_DC){ 2 FILE *fp = (FILE *) rsrc->ptr; 3 fclose(fp); 4 }
我們發(fā)現(xiàn)該函數(shù)的參數(shù)類型是zend_resource。這是 PHP7 新增的數(shù)據(jù)結(jié)構(gòu),在 PHP 5 則是zend_rsrc_list_entry。細(xì)節(jié)的內(nèi)容,我們留在后面分析。
1.1.3 在PHP_MINIT_FUNCTION中注冊
我們知道在 PHP 生命周期中,當(dāng) PHP 被裝載時,PHP_MINIT_FUNCTION(模塊啟動函數(shù))即被引擎調(diào)用。這使得引擎做一些例如資源類型,注冊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在前面已經(jīng)定義了,是該擴(kuò)展的別名(具體可以對比著代碼 tipi_file.c 查看鏈接描述
1.2 注冊資源
1.2.1 注冊資源 API
在 PHP 7 中刪除了原來的ZEND_REGISTER_RESOURCE宏,直接使用zend_register_resource函數(shù)
1 ZEND_API zend_resource* zend_register_resource(void *rsrc_pointer, int rsrc_type)
參數(shù) 解釋
rsrc_pointer 資源數(shù)據(jù)指針
rsrc_type 注冊資源類型時獲得的資源類型 id
1.2.2 在 file_open函數(shù)中實(shí)現(xiàn)資源的注冊
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 宏取代標(biāo)準(zhǔn) C 文件操作函數(shù) 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作為返回值。也就是說該函數(shù)的返回值為zval指針。RETURN_RES(zend_register_resource(fp, le_tipi_file))會將返回值的value.res設(shè)為fp,u1.type_info設(shè)為IS_RESOURCE_EX。大家可以根據(jù)源碼非常直觀的了解到,這里不粘貼代碼詳細(xì)說明了。
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宏,直接使用函數(shù)zend_fetch_resource,而且解析方式也變得簡單了很多,想比 PHP 5 要高效很多,后面我們再通過圖片分析對比。
參數(shù) 含義
res 資源指針
resource_type_name 該類資源的字符串別名
resource_type 該類資源的類型 id
1.3.2 解析資源的實(shí)現(xiàn)
當(dāng)我們要實(shí)現(xiàn)文件的讀取時,最終還是需要使用原生的fread函數(shù),所以這里需要通過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 }
這里需要說明,腳本自動生成的擴(kuò)展代碼中還是使用ZEND_FETCH_RESOURCE, 是個 BUG,因?yàn)樽詣由傻哪_本(ext/skeleton/create_stubs)還沒更新。
與之類似的文件的寫入操作,也很類似,這里就復(fù)制代碼了,請查看完整的代碼 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 看似非常簡單,實(shí)際做了很多工作,后面原理分析細(xì)說。
1.4.2 資源刪除的實(shí)現(xiàn)
我們在函數(shù)file_close中需要調(diào)用資源刪除 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 編譯安裝
通過上面的編碼,一個簡單的第三方的擴(kuò)展就實(shí)現(xiàn)了。查看完整版鏈接描述
下面的一些命令配置請根據(jù)自己的環(huán)境而定(安裝的過程可以參考最基礎(chǔ)的擴(kuò)展開發(fā)教程鏈接描述
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));
然后通過命令行執(zhí)行
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是一個全局靜態(tài)HashTable,資源類型注冊時,將一個zval結(jié)構(gòu)體變量zv存放入list_destructors的arData中,而zv的value.ptr卻指向了zend_rsrc_list_dtors_entry *lde,lde中包含的該種資源釋放函數(shù)指針、持久資源的釋放函數(shù)指針,資源類型名稱,該資源在 hashtable 中的索引依據(jù) (resource_id)等。
而這里的resource_id則是該函數(shù)的返回值,所以后面我們在解析該類型變量時,都需要將resource_id帶上。
整個的注冊步驟可以總結(jié)為下圖:
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 }
該函數(shù)的功能則是將zend_list_insert返回的zval中的資源指針返回。Z_RES_P宏在Zend/zend_types.h中定義。
重點(diǎn)分析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,后面用來作為索引查詢的依據(jù)。
而ZVAL_NEW_RES宏是 PHP 7 新增的一套東西,把一個資源裝載到zval里去,因?yàn)镻HP 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)
代碼比較清晰,首先根據(jù)h,p,t新建了一個資源,然后一起存入了z這個zval的結(jié)構(gòu)體。(最后兩個宏前面剛剛討論過了)
最后就是zend_hash_index_add_new宏了,追蹤代碼發(fā)現(xiàn)其最后等價于調(diào)用的是
_zend_hash_index_add_or_update_i(&EG(regular_list), index, &zv, HASH_ADD | HASH_ADD_NEW ZEND_FILE_LINE_RELAY_CC)
關(guān)于HashTable的具體操作,這里暫不做細(xì)致的分析,后面多帶帶再多帶帶說
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 不同的地方,這里不是每次都進(jìn)來將其引用計(jì)數(shù)減一操作,而是直接調(diào)用zend_resource_dtor函數(shù)。 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); } } 如果引用計(jì)數(shù)已經(jīng)等于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中索引層層關(guān)聯(lián),找到該類資源的釋放回調(diào)函數(shù),然后對該資源執(zhí)行釋放回調(diào)函數(shù)。
而后面的從EG(regular_list)中刪除,則是通過res->handler做為索引的依據(jù)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/30305.html
摘要:重點(diǎn)分析其中宏,返回表的,后面用來作為索引查詢的依據(jù)。中資源的解析比中解析簡單快捷很多,得益于其結(jié)構(gòu)的改變。先從逆向通過其在中索引層層關(guān)聯(lián),找到該類資源的釋放回調(diào)函數(shù),然后對該資源執(zhí)行釋放回調(diào)函數(shù)。 PHP 擴(kuò)展開發(fā)的文章,我均已更新至《TIPI》本篇承接上篇 PHP7 使用資源包裹第三方擴(kuò)展的實(shí)現(xiàn) PHP7 使用資源包裹第三方擴(kuò)展原理分析 注冊資源類型源碼 [c] ZEND_API ...
摘要:釋放用于在不同請求中始終存在的永久資源的函數(shù)。這是新增的數(shù)據(jù)結(jié)構(gòu),在則是。這使得引擎做一些例如資源類型,注冊變量等的一次初始化。也就是說該函數(shù)的返回值為指針。會將返回值的設(shè)為,設(shè)為。資源的刪除資源刪除傳入需要被刪除的資源即可。 PHP 擴(kuò)展開發(fā)的文章,我均已更新至《TIPI》 在閱讀下面的內(nèi)容之前,我們假定你已經(jīng)對 PHP 7 基本的數(shù)據(jù)結(jié)構(gòu) 都有大致的了解了,這是下面內(nèi)容閱讀的前提。...
摘要:隨后,執(zhí)行官給出一張當(dāng)張三存款發(fā)生變化之時,此機(jī)構(gòu)的運(yùn)作時序圖的確,小機(jī)構(gòu)靠人力運(yùn)作,大機(jī)構(gòu)才靠制度運(yùn)轉(zhuǎn)。第一條語句創(chuàng)建觀察員第一條語句張三我們調(diào)用的時候,就創(chuàng)建了對象,對象的所有屬性都將被拷貝至一個克隆對象并將克隆對象轉(zhuǎn)變成可觀察的。 ================前言=================== 初衷:網(wǎng)上已有很多關(guān)于 MobX 源碼解讀的文章,但大多閱讀成本甚高。...
摘要:那些瑣碎的知識點(diǎn)作者記錄的的很奇特很難記的知識點(diǎn)。易錯知識點(diǎn)整理注意和的區(qū)別中和都是輸出的作用,但是兩者之間還是有細(xì)微的差別。今天手頭不忙,總結(jié)一下,分享過程中掌握的知識點(diǎn)。 深入理解 PHP 之:Nginx 與 FPM 的工作機(jī)制 這篇文章從 Nginx 與 FPM 的工作機(jī)制出發(fā),探討配置背后的原理,讓我們真正理解 Nginx 與 PHP 是如何協(xié)同工作的。 PHP 那些瑣碎的知識...
閱讀 2298·2021-10-09 09:41
閱讀 1752·2019-08-30 15:53
閱讀 995·2019-08-30 15:52
閱讀 3449·2019-08-30 11:26
閱讀 775·2019-08-29 16:09
閱讀 3431·2019-08-29 13:25
閱讀 2266·2019-08-26 16:45
閱讀 1939·2019-08-26 11:51