天天看點

PHP7 使用資源包裹第三方擴充的實作及其源碼解讀

我們分為兩大塊:

首先實作一個自定義的檔案打開、讀取、寫入、關閉的檔案操作擴充;

首先進入到源碼目錄的<code>ext</code>目錄中,添加一個檔案操作的原型檔案

編輯原型為

生成骨架

這樣一個簡單的檔案操作擴充的代碼骨架就生成了。

參數

解釋

ld

釋放該資源時調用的函數。

pld

釋放用于在不同請求中始終存在的永久資源的函數。

type_name

是一個具有描述性類型名稱的字元串。

module_number

為引擎内部使用,當我們調用這個函數時,我們隻需要傳遞一個已經定義好的<code>module_number</code>變量。

該 api 傳回一個資源類型 id,該id應當被作為全局變量儲存在擴充裡,以便在必要的時候傳遞給其他資源api。

1.1.2 添加資源釋放回調函數

我們發現該函數的參數類型是<code>zend_resource</code>。這是 php7 新增的資料結構,在 php 5 則是<code>zend_rsrc_list_entry</code>。細節的内容,我們留在後面分析。

我們知道在 php 生命周期中,當 php 被裝載時,<code>php_minit_function</code>(子產品啟動函數)即被引擎調用。這使得引擎做一些例如資源類型,注冊ini變量等的一次初始化。

那麼我們需要在這裡通過<code>zend_register_list_destructors_ex</code>在<code>php_minit_function</code>來注冊資源類型。

在 php 7 中删除了原來的<code>zend_register_resource</code>宏,直接使用<code>zend_register_resource</code>函數

rsrc_pointer

資源資料指針

rsrc_type

注冊資源類型時獲得的資源類型 id

其中<code>return_res</code>宏的作用是将傳回的<code>zend_resource</code>添加到<code>zval</code>中,然後将最後的<code>zval</code>作為傳回值。也就是說該函數的傳回值為<code>zval</code>指針。<code>return_res(zend_register_resource(fp, le_tipi_file))</code>會将傳回值的<code>value.res</code>設為<code>fp</code>,<code>u1.type_info</code>設為<code>is_resource_ex</code>。大家可以根據源碼非常直覺的了解到,這裡不粘貼代碼詳細說明了。

在 php 7 中删除了原有的<code>zend_fetch_resource</code>宏,直接使用函數<code>zend_fetch_resource</code>,而且解析方式也變得簡單了很多,想比 php 5 要高效很多,後面我們再通過圖檔分析對比。

含義

res

資源指針

resource_type_name

該類資源的字元串别名

resource_type

該類資源的類型 id

當我們要實作檔案的讀取時,最終還是需要使用原生的<code>fread</code>函數,是以這裡需要通過<code>zend_fetch_resource</code>将<code>zend_resource</code>解析成為該資源包裹的原始的<code>file *</code>的指針。

這裡需要說明,腳本自動生成的擴充代碼中還是使用<code>zend_fetch_resource</code>, 是個 bug,因為自動生成的腳本(<code>ext/skeleton/create_stubs</code>)還沒更新。

傳入需要被删除的資源即可。該 api 看似非常簡單,實際做了很多工作,後面原理分析細說。

我們在函數<code>file_close</code>中需要調用資源删除 api

直接用 php 腳本測試,就不一個功能一個功能寫測試樣例了,修改<code>tipi_file.php</code>檔案。

然後通過指令行執行

其中

展開後,等價于

<code>list_destructors</code>是一個全局靜态<code>hashtable</code>,資源類型注冊時,将一個<code>zval</code>結構體變量<code>zv</code>存放入<code>list_destructors</code>的<code>ardata</code>中,而<code>zv</code>的<code>value.ptr</code>卻指向了<code>zend_rsrc_list_dtors_entry *lde</code>,<code>lde</code>中包含的該種資源釋放函數指針、持久資源的釋放函數指針,資源類型名稱,該資源在 hashtable 中的索引依據 (<code>resource_id</code>)等。

而這裡的<code>resource_id</code>則是該函數的傳回值,是以後面我們在解析該類型變量時,都需要将<code>resource_id</code>帶上。

整個的注冊步驟可以總結為下圖:

PHP7 使用資源包裹第三方擴充的實作及其源碼解讀

該函數的功能則是将<code>zend_list_insert</code>傳回的<code>zval</code>中的資源指針傳回。<code>z_res_p</code>宏在<code>zend/zend_types.h</code>中定義。

重點分析<code>zend_list_insert</code>

其中<code>zend_hash_next_free_element</code>宏,傳回<code>&amp;eg(regular_list)</code>表的<code>nnextfreeelement</code>,後面用來作為索引查詢的依據。

而<code>zval_new_res</code>宏是 php 7 新增的一套東西,把一個資源裝載到<code>zval</code>裡去,因為php 7 中<code>bucket</code>隻能存<code>zval</code>了。

代碼比較清晰,首先根據<code>h</code>,<code>p</code>,<code>t</code>建立了一個資源,然後一起存入了<code>z</code>這個zval的結構體。(最後兩個宏前面剛剛讨論過了)

最後就是<code>zend_hash_index_add_new</code>宏了,追蹤代碼發現其最後等價于調用的是

關于<code>hashtable</code>的具體操作,這裡暫不做細緻的分析,後面單獨再單獨說。

PHP7 使用資源包裹第三方擴充的實作及其源碼解讀

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

首先通過<code>z_res_p</code>宏,擷取<code>filehandle</code>這個<code>zval</code>變量中的<code>zend_resource</code>。然後<code>zend_fetch_resource</code>中隻是對比了<code>zend_resource</code>的<code>type</code>與我們預想的資源類型是否一緻,然後傳回了<code>zend_resource</code>的<code>*ptr</code>,最後轉換成<code>file *</code>指針。

php7 中資源的解析比 php5中解析簡單快捷很多,得益于其 zval 結構的改變。

原來php5中則需要通過<code>eg(regular_list)</code>查找,如下圖所示。

PHP7 使用資源包裹第三方擴充的實作及其源碼解讀

而現在 php7的解析則直接從<code>zval</code>裡解析出<code>zend_resource</code>,如下圖所示:

PHP7 使用資源包裹第三方擴充的實作及其源碼解讀

與php5 不同的地方,這裡不是每次都進來将其引用計數減一操作,而是直接調用<code>zend_resource_dtor</code>函數。

如果引用計數已經等于0或者小于0了,那麼才從<code>eg(regular_list)</code>中删除

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

PHP7 使用資源包裹第三方擴充的實作及其源碼解讀

先從<code>zend_resource</code>逆向通過其<code>type</code>在<code>list_destructors</code>中索引層層關聯,找到該類資源的釋放回調函數,然後對該資源執行釋放回調函數。

而後面的從<code>eg(regular_list)</code>中删除,則是通過<code>res-&gt;handler</code>做為索引的依據。

上一篇: shell
下一篇: 雲計算