PHP7 使用資源包裹第三方擴(kuò)展的實(shí)現(xiàn)及其源碼解讀

在閱讀下面的內(nèi)容之前,我假定已看到的人已經(jīng)對 PHP 7 基本的數(shù)據(jù)結(jié)構(gòu)都有大致的了解了玩祟,這是下面內(nèi)容閱讀的前提腹缩。

我們分為兩大塊:

首先實(shí)現(xiàn)一個(gè)自定義的文件打開、讀取空扎、寫入、關(guān)閉的文件操作擴(kuò)展润讥;

然后分析各個(gè)操作背后的實(shí)現(xiàn)原理转锈,其中某些部分的實(shí)現(xiàn)我會(huì)和PHP 5.3 使用資源包裹第三方擴(kuò)展源碼解讀對比分析。

0 通過原型生成擴(kuò)展骨架

首先進(jìn)入到源碼目錄的ext目錄中楚殿,添加一個(gè)文件操作的原型文件

[root@localhost?php-src-php-7.0.3]#?cd?ext/

[root@localhost?ext]#?vim?tipi_file.proto

編輯原型為

resource?file_open(string?filename,?string?mode)

string?file_read(resource?filehandle,?int?size)

bool?file_write(resource?filehandle,?string?buffer)

bool?file_close(resource?filehandle)

[root@localhost?ext]#?./ext_skel?--extname=tipi_file?--proto=./tipi_file.proto

這樣一個(gè)簡單的文件操作擴(kuò)展的代碼骨架就生成了撮慨。

完整代碼tipi_file.c(https://github.com/zhoumengkang/notes/blob/master/php-extension/php7.0/tipi_file/tipi_file.c),可以先有一個(gè)大致的了解脆粥,這樣后面閱讀時(shí)砌溺,思路可能會(huì)清晰很多。

1 擴(kuò)展的實(shí)現(xiàn)

1.1?注冊資源類型

1.1.1 注冊資源 API

ZEND_APIintzend_register_list_destructors_ex(rsrc_dtor_func_t?ld,?rsrc_dtor_func_t?pld,constchar*type_name,intmodule_number)

參數(shù)解釋

ld釋放該資源時(shí)調(diào)用的函數(shù)变隔。

pld釋放用于在不同請求中始終存在的永久資源的函數(shù)规伐。

type_name是一個(gè)具有描述性類型名稱的字符串。

module_number為引擎內(nèi)部使用匣缘,當(dāng)我們調(diào)用這個(gè)函數(shù)時(shí)猖闪,我們只需要傳遞一個(gè)已經(jīng)定義好的module_number變量。

該 API 返回一個(gè)資源類型 id肌厨,該id應(yīng)當(dāng)被作為全局變量保存在擴(kuò)展里培慌,以便在必要的時(shí)候傳遞給其他資源API。

1.1.2 添加資源釋放回調(diào)函數(shù)

staticvoidtipi_file_dtor(zend_resource?*rsrc?TSRMLS_DC){

FILE*fp?=?(FILE*)?rsrc->ptr;

fclose(fp);

}

我們發(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 被裝載時(shí)进胯,PHP_MINIT_FUNCTION(模塊啟動(dòng)函數(shù))即被引擎調(diào)用用爪。這使得引擎做一些例如資源類型,注冊INI變量等的一次初始化胁镐。

那么我們需要在這里通過zend_register_list_destructors_ex在PHP_MINIT_FUNCTION來注冊資源類型偎血。

PHP_MINIT_FUNCTION(tipi_file)

{

/*?If?you?have?INI?entries,?uncomment?these?lines

REGISTER_INI_ENTRIES();

*/

le_tipi_file?=?zend_register_list_destructors_ex(tipi_file_dtor,?NULL,?TIPI_FILE_TYPE,?module_number);

returnSUCCESS;

}

其中TIPI_FILE_TYPE在前面已經(jīng)定義了诸衔,是該擴(kuò)展的別名(具體可以對比著代碼 tipi_file.c 查看(https://github.com/zhoumengkang/notes/blob/master/php-extension/php7.0/tipi_file/tipi_file.c))

1.2 注冊資源

1.2.1 注冊資源 API

在 PHP 7 中刪除了原來的ZEND_REGISTER_RESOURCE宏,直接使用zend_register_resource函數(shù)

ZEND_API?zend_resource*?zend_register_resource(void*rsrc_pointer,intrsrc_type)

參數(shù)解釋

rsrc_pointer資源數(shù)據(jù)指針

rsrc_type注冊資源類型時(shí)獲得的資源類型 id

1.2.2 在 file_open函數(shù)中實(shí)現(xiàn)資源的注冊

PHP_FUNCTION(file_open)

{

char*filename?=?NULL;

char*mode?=?NULL;

intargc?=?ZEND_NUM_ARGS();

size_tfilename_len;

size_tmode_len;

if(zend_parse_parameters(argc?TSRMLS_CC,"ss",?&filename,?&filename_len,?&mode,?&mode_len)?==?FAILURE)

return;

//?使用?VCWD?宏取代標(biāo)準(zhǔn)?C?文件操作函數(shù)

FILE*fp?=?VCWD_FOPEN(filename,?mode);

if(fp?==?NULL)?{

RETURN_FALSE;

}

RETURN_RES(zend_register_resource(fp,?le_tipi_file));

}

其中RETURN_RES宏的作用是將返回的zend_resource添加到zval中颇玷,然后將最后的zval作為返回值笨农。也就是說該函數(shù)的返回值為zval指針。RETURN_RES(zend_register_resource(fp, le_tipi_file))會(huì)將返回值的value.res設(shè)為fp帖渠,u1.type_info設(shè)為IS_RESOURCE_EX谒亦。大家可以根據(jù)源碼非常直觀的了解到,這里不粘貼代碼詳細(xì)說明了空郊。

1.3 使用資源

1.3.1 使用資源 API

ZEND_APIvoid*zend_fetch_resource(zend_resource?*res,constchar*resource_type_name,intresource_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)文件的讀取時(shí)哼审,最終還是需要使用原生的fread函數(shù)谐腰,所以這里需要通過zend_fetch_resource將zend_resource解析成為該資源包裹的原始的FILE *的指針。

PHP_FUNCTION(file_read)

{

intargc?=?ZEND_NUM_ARGS();

intfilehandle_id?=?-1;

zend_long?size;

zval?*filehandle?=?NULL;

FILE*fp?=?NULL;

char*result;

size_tbytes_read;

if(zend_parse_parameters(argc?TSRMLS_CC,"rl",?&filehandle,?&size)?==?FAILURE)

return;

if((fp?=?(FILE*)zend_fetch_resource(Z_RES_P(filehandle),?TIPI_FILE_TYPE,?le_tipi_file))?==?NULL)?{

RETURN_FALSE;

}

result?=?(char*)?emalloc(size+1);

bytes_read?=fread(result,?1,?size,?fp);

result[bytes_read]?='\0';

RETURN_STRING(result,?0);

}

這里需要說明涩盾,腳本自動(dòng)生成的擴(kuò)展代碼中還是使用ZEND_FETCH_RESOURCE十气, 是個(gè) BUG,因?yàn)樽詣?dòng)生成的腳本(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_APIintzend_list_close(zend_resource?*res)

傳入需要被刪除的資源即可籍胯。該 API 看似非常簡單,實(shí)際做了很多工作离福,后面原理分析細(xì)說杖狼。

1.4.2 資源刪除的實(shí)現(xiàn)

我們在函數(shù)file_close中需要調(diào)用資源刪除 API

PHP_FUNCTION(file_close)

{

intargc?=?ZEND_NUM_ARGS();

intfilehandle_id?=?-1;

zval?*filehandle?=?NULL;

if(zend_parse_parameters(argc?TSRMLS_CC,"r",?&filehandle)?==?FAILURE)

return;

zend_list_close(Z_RES_P(filehandle));

RETURN_TRUE;

}

1.5 編譯安裝以及測試

1.5.1 編譯安裝

通過上面的編碼,一個(gè)簡單的第三方的擴(kuò)展就實(shí)現(xiàn)了妖爷。查看完整版(https://github.com/zhoumengkang/notes/tree/master/php-extension/php7.0/tipi_file)

下面的一些命令配置請根據(jù)自己的環(huán)境而定(安裝的過程可以參考最基礎(chǔ)的擴(kuò)展開發(fā)教程(https://mengkang.net/660.html))

[root@localhost?tipi_file]#?php7ize

Configuringfor:

PHP?Api?Version:?????????20151012

Zend?Module?Api?No:??????20151012

Zend?Extension?Api?No:???320151012

[root@localhost?tipi_file]#?./configure?--with-php-config=/usr/local/php7/bin/php-config

...

[root@localhost?tipi_file]#?make

...

[root@localhost?tipi_file]#?make?install

...

1.5.2 測試

直接用 php 腳本測試蝶涩,就不一個(gè)功能一個(gè)功能寫測試樣例了,修改tipi_file.php文件絮识。

$fp?=?file_open("./CREDITS","r+");

var_dump($fp);

var_dump(file_read($fp,6));

var_dump(file_write($fp,"zhoumengakng"));

var_dump(file_close($fp));

然后通過命令行執(zhí)行

php7?-d"extension=tipi_file.so"tipi_file.php

2 源碼分析

2.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)

{

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)?{

returnFAILURE;

}

returnlist_destructors.nNextFreeElement-1;

}

其中

ZVAL_PTR(&zv,?lde);

等價(jià)于

zv.value.ptr?=?(lde);

zv.u1.type_info?=?IS_PTR;

list_destructors是一個(gè)全局靜態(tài)HashTable绿聘,資源類型注冊時(shí),將一個(gè)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ù)的返回值,所以后面我們在解析該類型變量時(shí)哲思,都需要將resource_id帶上洼畅。

整個(gè)的注冊步驟可以總結(jié)為下圖:

2.2 資源的注冊

ZEND_API?zend_resource*?zend_register_resource(void*rsrc_pointer,intrsrc_type)

{

zval?*zv;

zv?=?zend_list_insert(rsrc_pointer,?rsrc_type);

returnZ_RES_P(zv);

}

該函數(shù)的功能則是將zend_list_insert返回的zval中的資源指針返回。Z_RES_P宏在Zend/zend_types.h中定義棚赔。

重點(diǎn)分析zend_list_insert

ZEND_API?zval?*zend_list_insert(void?*ptr,?inttype)

{

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);

returnzend_hash_index_add_new(&EG(regular_list),?index,?&zv);

}

其中zend_hash_next_free_element宏帝簇,返回&EG(regular_list)表的nNextFreeElement,后面用來作為索引查詢的依據(jù)靠益。

而ZVAL_NEW_RES宏是 PHP 7 新增的一套東西丧肴,把一個(gè)資源裝載到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新建了一個(gè)資源,然后一起存入了z這個(gè)zval的結(jié)構(gòu)體绩卤。(最后兩個(gè)宏前面剛剛討論過了)

最后就是zend_hash_index_add_new宏了,追蹤代碼發(fā)現(xiàn)其最后等價(jià)于調(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ì)致的分析濒憋,后面單獨(dú)再單獨(dú)說。

2.3 解析資源源碼分析

ZEND_APIvoid*zend_fetch_resource(zend_resource?*res,constchar*resource_type_name,intresource_type)

{

if(resource_type?==?res->type)?{

returnres->ptr;

}

if(resource_type_name)?{

constchar*space;

constchar*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);

}

returnNULL;

}

在上面的例子中我們是這樣解析的

(FILE*)zend_fetch_resource(Z_RES_P(filehandle),?TIPI_FILE_TYPE,?le_tipi_file)

而現(xiàn)在 PHP7的解析則直接從zval里解析出zend_resource陶夜,如下圖所示:

2.4 刪除資源源碼分析

ZEND_APIintzend_list_close(zend_resource?*res)

{

if(GC_REFCOUNT(res)?<=?0)?{

returnzend_list_free(res);

}elseif(res->type?>=?0)?{

zend_resource_dtor(res);

}

returnSUCCESS;

}

與PHP5 不同的地方凛驮,這里不是每次都進(jìn)來將其引用計(jì)數(shù)減一操作,而是直接調(diào)用zend_resource_dtor函數(shù)条辟。

staticvoidzend_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_APIintzend_list_free(zend_resource?*res)

{

if(GC_REFCOUNT(res)?<=?0)?{

returnzend_hash_index_del(&EG(regular_list),?res->handle);

}else{

returnSUCCESS;

}

}

原理圖還是引用上面的注冊資源類型、并注冊資源的圖:

先從zend_resource逆向通過其type在list_destructors中索引層層關(guān)聯(lián)羽嫡,找到該類資源的釋放回調(diào)函數(shù)本姥,然后對該資源執(zhí)行釋放回調(diào)函數(shù)。

而后面的從EG(regular_list)中刪除杭棵,則是通過res->handler做為索引的依據(jù)婚惫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市魂爪,隨后出現(xiàn)的幾起案子先舷,更是在濱河造成了極大的恐慌,老刑警劉巖滓侍,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒋川,死亡現(xiàn)場離奇詭異,居然都是意外死亡撩笆,警方通過查閱死者的電腦和手機(jī)捺球,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門缸浦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人懒构,你說我怎么就攤上這事餐济。” “怎么了胆剧?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵絮姆,是天一觀的道長。 經(jīng)常有香客問我秩霍,道長篙悯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任铃绒,我火速辦了婚禮鸽照,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颠悬。我一直安慰自己矮燎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布赔癌。 她就那樣靜靜地躺著诞外,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灾票。 梳的紋絲不亂的頭發(fā)上峡谊,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音刊苍,去河邊找鬼既们。 笑死,一個(gè)胖子當(dāng)著我的面吹牛正什,可吹牛的內(nèi)容都是我干的啥纸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼埠忘,長吁一口氣:“原來是場噩夢啊……” “哼脾拆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起莹妒,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤名船,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后旨怠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渠驼,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年鉴腻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迷扇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片百揭。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蜓席,靈堂內(nèi)的尸體忽然破棺而出器一,到底是詐尸還是另有隱情,我是刑警寧澤厨内,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布祈秕,位于F島的核電站,受9級特大地震影響雏胃,放射性物質(zhì)發(fā)生泄漏请毛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一瞭亮、第九天 我趴在偏房一處隱蔽的房頂上張望方仿。 院中可真熱鬧,春花似錦统翩、人聲如沸仙蚜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鳍征。三九已至,卻和暖如春面徽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背匣掸。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工趟紊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碰酝。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓霎匈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親送爸。 傳聞我的和親對象是個(gè)殘疾皇子铛嘱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容