PHP資源類型

在PHP中,咱們常常使用到資源類型變量。例如:mysql鏈接、文件句柄等。php

這些變量沒法使用標量來表示,那麼在Zend內核中是如何將PHP中的資源變量與C語言中的資源銜接的呢?mysql


1、資源變量在PHP中的使用sql

  1. $fp = fopen("test.txt", "rw");  
  2.   
  3. var_dump($fp);  
  4.   
  5. fclose($fp);  


打印結果:resource(5) of type (stream)數據庫

數字5:表示資源ID爲5,具體含義後面介紹。函數

stream:資源類型名稱。php-fpm


2、資源IDspa

內核中將註冊的資源變量存儲在一個HashTable中,並把資源所在HashTable中的key做爲資源ID。.net

因此,實際上PHP中的資源變量實際存儲的是一個整型,經過這個ID找到HashTable中對應的資源。指針

  1. #define Z_RESVAL(zval)          (zval).value.lval  
  2. #define Z_RESVAL_P(zval)        Z_RESVAL(*zval)  
  3. #define Z_RESVAL_PP(zval)       Z_RESVAL(**zval)  

上面的宏,是內核中ZE爲資源變量賦值的API,看出確實是對整型變量的賦值。blog


3、資源類型名稱

爲了區分資源類型,須要爲咱們定義的資源定義類型名稱。

  1. #define MY_RES_NAME "my_resource" //資源類型名稱,PHP經過var_dump打印資源變量時會看到這個名稱  
  2. static int my_resource_descriptor;  
  3.   
  4. ZEND_MINIT_FUNCTION(jinyong)  
  5. {  
  6.     my_resource_descriptor = zend_register_list_destructors_ex(NULL, NULL, MY_RES_NAME, module_number);//向內核中註冊新的資源類型  
  7. }  


ZEND_MINIT_FUNCTION(jinyong)會在PHP做爲SAPI(例如,Apache的mod_php5擴展)被加載到內存時,會執行全部擴展的ZEND_MINIT_FUNCTION。

其中jinyong,是當前擴展的名字。例如此時擴展的名字就是jinyong

這裏爲了方便理解,咱們就把它認爲是擴展在初始化時,會向內核中註冊新的資源類型。


4、建立資源變量

資源類型已經註冊成功,也爲資源定義了區分的類型名稱。如今能夠使用這種資源的變量了。

實現PHP中的fopen函數:

  1. PHP_FUNCTION(my_fopen)  
  2. {  
  3.     zval *res;  
  4.   
  5.     char *filename, *mode;  
  6.       
  7.     int filename_strlen, mode_strlen;  
  8.   
  9.     FILE *fp;  
  10.       
  11.     if(zend_parse_parameters(ZEND_NUM_ARGS TSRMLS_CC, "s|s",  &filename, &filename_strlen, &mode, &mode_strlen) == FAILURE){  
  12.         RETURN_FALSE;  
  13.     }  
  14.   
  15.     //此處省略了對參數的有效性驗證  
  16.     fp = fopen(filename, mode);  
  17.   
  18.     ZEND_REGISTER_RESOURCE(res, fp, my_resource_descriptor);//向全局變量&EG(regular_list)中註冊資源變量,並將對應HashTable的ID賦值給res  
  19.   
  20.     RETURN_RESOURCE(res);//向PHP返回資源變量  
  21. }  

這裏,定義了PHP中名稱爲my_fopen的函數。my_fopen(string $file_name, string $mode)


實現PHP中的fclose函數:

  1. PHP_FUNCTION(my_fclose)  
  2. {  
  3.     zval *res;  
  4.       
  5.     FILE *fp;  
  6.   
  7.     if(zend_parse_parameters(ZEND_NUM_ARGS TSRMS_CC, "r", &res) == FAILURE){  
  8.         RETURN_FALSE;  
  9.     }  
  10.   
  11.     if(Z_TYPE_P(res) == IS_RESOURCE){//判斷變量類型是不是資源類型  
  12.         zend_hash_index_del(&EG(regular_list), Z_RESVAL_P(res));//EG就相似於PHP中的$_GLOBALS。在全局資源變量regular_list中刪除對應ID的資源  
  13.     }else{  
  14.         php_error_docref(NULL TSRMLS_CC, E_WARNING, "參數必須是資源類型變量");  
  15.         RETURN_FALSE;  
  16.     }  
  17.   
  18.     RETURN_TRUE;  
  19. }  

定義了PHP中名稱爲my_fclose的函數。my_fclose($resource)

5、編譯、安裝擴展,重啓php-fpm或mod_php5等


6、PHP中使用自定義擴展中的方法

  1. my_fwrite($fp, "aaTest");  
  2.   
  3. var_dump($fp);  
  4.   
  5. my_fclose($fp);  
  6.   
  7. var_dump($fp);  

能夠正常,打開和關閉資源。


7、咱們在PHP中常常使用數據庫鏈接資源、文件句柄資源,但他們一般無需咱們手工釋放,也不會出現內存泄漏問題,這是如何實現的呢?

  1. my_resource_descriptor = zend_register_list_destructors_ex(NULL, NULL, MY_RES_NAME, module_number);//向內核中註冊新的資源類型  

回到最開始的註冊資源類型,看到zend_register_list_destructors_ex的第一個參數,這個參數就是析構函數的指針。

那麼,若是須要實現自動釋放功能,只須要定義析構函數並傳遞函數指針便可。


再看一個問題:

  1. $fp = fopen("test.txt", "rw");  
  2.   
  3. var_dump($fp);  
  4.   
  5. //fclose($fp); 此處不使用fclose釋放資源  
  6.   
  7. unset($fp); //而是使用unset釋放  
  8. //unset沒有問題,會正常釋放$fp變量。但$fp對應真正的打開文件資源句柄資源將永遠釋放不了,直至mod_php5或php-fpm重啓  
  9. //能夠看出,在註冊資源類型時定義析構函數的必要性了  


定義析構函數:

  1. static void php_myres_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){//析構函數被調用時,會接受一個當前資源變量的參數  
  2.     FILE *fp = (FILE*)rsrc->ptr;  
  3.     fclose(fp);  
  4. }  
  5.   
  6. ZEND_MINIT_FUNCTION(jinyong)  
  7. {  
  8.     my_resource_descriptor = zend_register_list_destructors_ex(php_myres_dtor, NULL, MY_RES_NAME, module_number);  
  9. }  
    1. 在PHP中,所謂資源變量,實際都是經過存儲整型值,在到內核全局資源變量列表EG(regular_list)中找到對應的指針,並進行相應操做。  
    2. 而PHP資源變量,之因此不用擔憂相似MYSQL鏈接未釋放問題,也是由於擴展中定義了析構方法,幫助自動釋放。 
相關文章
相關標籤/搜索