我們分為兩大塊:
首先實作一個自定義的檔案打開、讀取、寫入、關閉的檔案操作擴充;
首先進入到源碼目錄的<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>帶上。
整個的注冊步驟可以總結為下圖:

該函數的功能則是将<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>&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>的具體操作,這裡暫不做細緻的分析,後面單獨再單獨說。
在上面的例子中我們是這樣解析的
首先通過<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的解析則直接從<code>zval</code>裡解析出<code>zend_resource</code>,如下圖所示:
與php5 不同的地方,這裡不是每次都進來将其引用計數減一操作,而是直接調用<code>zend_resource_dtor</code>函數。
如果引用計數已經等于0或者小于0了,那麼才從<code>eg(regular_list)</code>中删除
原理圖還是引用上面的注冊資源類型、并注冊資源的圖:
先從<code>zend_resource</code>逆向通過其<code>type</code>在<code>list_destructors</code>中索引層層關聯,找到該類資源的釋放回調函數,然後對該資源執行釋放回調函數。
而後面的從<code>eg(regular_list)</code>中删除,則是通過<code>res->handler</code>做為索引的依據。