1. 程式人生 > >王帥:深入PHP核心(二)——SAPI探究

王帥:深入PHP核心(二)——SAPI探究

http://www.csdn.net/article/2014-09-26/2821885-exploring-of-the-php-2

SAPI是Server Application Programming Interface(伺服器應用程式設計介面)的縮寫。PHP通過SAPI提供了一組介面,供應用和PHP核心之間進行資料互動。

簡單的講,就像函式的輸入和輸出一樣,我們通過Linux命令列執行一段PHP程式碼,本質是Linux的Shell通過PHP的SAPI傳入一組引數,Zend引擎執行後,返回給shell,由shell顯示出來的過程。同樣的,通過Apache呼叫PHP,通過Web伺服器給SAPI傳入資料,Zend引擎執行後,返回給Apache,由Apache顯示在頁面上。

 

圖1. PHP架構圖 

PHP提供很多種形式的介面,包括apache、apache2filter、apache2handler、caudium、cgi 、cgi-fcgi、cli、cli-server、continuity、embed、isapi、litespeed、milter、nsapi、phttpd pi3web、roxen、thttpd、tux和webjames。但是常用的只有5種形式,CLI/CGI(命令列)、Multiprocess(多程序)、Multithreaded(多執行緒)、FastCGI和Embedded(內嵌)。

PHP提供了一個函式檢視當前SAPI介面型別:

  1. string php_sapi_name ( void )  

PHP的執行和載入

無論使用哪種SAPI,在PHP執行指令碼前後,都包含一系列事件:Module的Init(MINT)和Shutdown(MSHUTDOWN),Request 的Init(RINT)和Shutdown(RSHUTDOWN)。 第一階段是PHP模組初始化階段(MINT),可以初始化擴充套件內部變數、分配資源和註冊資源處理器,在整個PHP例項生命週期內,該過程只執行一次。

什麼是PHP模組?通過上面的PHP架構圖,在PHP中可以使用get_loaded_extensions 函式來檢視所有編譯並載入的模組/擴充套件,相當於CLI模式下的php -m。

以PHP的Memcached擴充套件原始碼為例:

  1. PHP_MINIT_FUNCTION(memcached) {       
  2.     zend_class_entry ce;      
  3.     memcpy(&memcached_object_handlers,zend_get_std_object_handlers(), sizeof(zend_object_handlers));      
  4. memcached_object_handlers.clone_obj = NULL;     /* 執行了一些類似的初始化操作 */
  5. return SUCCESS;   
  6. }  
第二階段是請求初始化階段(RINT),在模組初始化並激活後,會建立PHP執行環境,同時呼叫所有模組註冊的RINT函式,呼叫每個擴充套件的請求初始化函式 ,設定特定的環境變數、分配資源或執行其他任務,如稽核。
  1. PHP_RINIT_FUNCTION(memcached) {       
  2.     /* 執行一些關於請求的初始化 */
  3.     return SUCCESS;   
  4. }  

第三階段,請求處理完成後,會呼叫PHP_RSHUTDOWN_FUNCTION進行回收,這是每個擴充套件的請求關閉函式,執行最後的清理工作。Zend引擎執行清理過程、垃圾收集、對之前的請求期間用到的每個變數執行unset。請求完成可能是執行到指令碼完成,也可能是呼叫die()或exit()函式完成

第四階段,當PHP生命週期結束時候,PHP_MSHUTDOWN_FUNCTION對模組進行回收處理,這是每個擴充套件的模組關閉函式,用於關閉自己的核心子系統。

  1. PHP_MSHUTDOWN_FUNCTION(memcached) { /* 執行關於模組的銷燬工作 */ UNREGISTER_INI_ENTRIES(); return SUCCESS; }  

常見的執行模式

常見的SAPI模式有五種:

  • CLI和CGI模式(單程序模式)
  • 多程序模式
  • 多執行緒模式
  • FastCGI模式
  • 嵌入式

1. CLI/CGI模式

CLI和CGI都屬於單程序模式,PHP的生命週期在一次請求中完成。也就是說每次執行PHP指令碼,都會執行第二部分講的四個INT和Shutdown事件。

圖2. CGI/CLI生命週期 

2. 多程序模式(Multiprocess)

多程序模式可以將PHP內建到Web Server中,PHP可以編譯成Apache下的prefork MPM模式和APXS模組,當Apache啟動後,會fork很多子程序,每個子程序擁有自己獨立的程序地址空間。

 

圖3. 多程序模式生命週期 

在一個子程序中,PHP的生命週期是呼叫MINT啟動後,執行多次請求(RINT/RSHUTDOWN),在Apache關閉或程序結束後,才會呼叫MSHUTDOWN進行回收階段。 

 

圖4. 多程序的生命週期 

多程序模型中,每個子程序都是獨立執行,沒有程式碼和資料共享,因此一個子程序終止退出和重新生成,不會影響其他子程序的穩定。

3. 多執行緒模式(Multithreaded)

Apache2的Worker MPM採用了多執行緒模型,在一個程序下建立多個執行緒,在同一個程序地址空間執行。


圖5. 多執行緒生命週期

4. FastCGI模式

在我們用的Nginx+PHP-FPM用的就是FastCGI模式,Fastcgi是一種特殊的CGI模式,是一種常駐程序型別的CGI,執行後可以Fork多個程序,不用花費時間動態的Fork子程序,也不需要每次請求都呼叫MINT/MSHUTDOWN。PHP通過PHP-FPM來管理和排程FastCGI的程序池。Nginx和PHP-FPM通過本地的TCP Socket和Unix Socket 進行通訊。

 

圖6. FastCGI模式生命週期

PHP-FPM程序管理器自身初始化,啟動多個CGI直譯器程序等待來自Nginx的請求。當客戶端請求達到PHP-FPM,管理器選擇到一個CGI程序進行處理,Nginx將CGI環境變數和標準輸入傳送到一個PHP-CIG子程序。PHP-CGI子程序處理完成後,將標準輸出和錯誤資訊返回給Nginx,當PHP-CGI子程序關閉連線時,請求處理完成。PHP-CGI子程序等待著下一個連線。

可以想象CGI的系統開銷有多大。每一個Web 請求PHP都必須重新解析php.ini、載入全部擴充套件並始化全部資料結構。使用FastCGI,所有這些都只在程序啟動時發生一次。另外,對於資料庫和Memcache的持續連線可以工作。

5. 內嵌模式(Embedded)

Embed SAPI是一種特殊的SAPI,允許在C/C++語言中呼叫PHP提供的函式。這種SAPI和CLI模式一樣,按照Module Init => Request Init => Request => Request Shutdown => Module Shutdown的模式執行。

Embed SAPI可以呼叫PHP豐富的類庫,也可以實現高階玩法,比如可以檢視PHP的OPCODE(PHP執行的中間碼,Zend引擎的指令,由PHP程式碼生成)。

SAPI的執行機制

我們以CGI為例,看一下SAPI的執行機制。

  1. static sapi_module_struct cgi_sapi_module = {       
  2.     "cgi-fcgi",                     /* 輸出給php_info()使用 */"CGI/FastCGI",                  /* pretty name */
  3.     php_cgi_startup,                /* startup 當SAPI初始化時,首先會呼叫該函式 */
  4.     php_module_shutdown_wrapper,    /* shutdown  關閉函式包裝器,它用來釋放所有的SAPI的資料結構、記憶體等,呼叫php_module_shutdown */
  5.     sapi_cgi_activate,              /* activate  此函式會在每個請求開始時呼叫,它會做初始化,資源分配 */
  6.     sapi_cgi_deactivate,            /* deactivate  此函式會在每個請求結束時呼叫,它用來確保所有的資料都得到釋放 */
  7.     sapi_cgi_ub_write,              /* unbuffered write  不快取的寫操作(unbuffered write),它是用來向SAPI外部輸出資料 */
  8.     sapi_cgi_flush,                 /* flush  重新整理輸出,在CLI模式下通過使用C語言的庫函式fflush實現*/     NULL,                           /* get uid */
  9.     sapi_cgi_getenv,                /* getenv 根據name查詢環境變數 */
  10.     php_error,                      /* error handler 註冊錯誤處理函式  */
  11.     NULL,                           /* header handler PHP呼叫header()時候被呼叫 */
  12.     sapi_cgi_send_headers,          /* send headers handler 傳送頭部資訊*/
  13.     NULL,                           /* send header handler 傳送一個單獨的頭部資訊 */
  14.     sapi_cgi_read_post,             /* read POST data  當請求的方法是POST時,程式獲取POST資料,寫入$_POST陣列 */
  15.     sapi_cgi_read_cookies,          /* read Cookies 獲取Cookie值  */
  16.     sapi_cgi_register_variables,    /* register server variables 給$_SERVER新增環境變數 */
  17.     sapi_cgi_log_message,           /* Log message 輸出錯誤資訊 */
  18.     NULL,                           /* Get request time */
  19.     NULL,                           /* Child terminate */
  20.     STANDARD_SAPI_MODULE_PROPERTIES   
  21. };   

由上面程式碼可見,PHP的SAPI像是面向物件中基類,SAPI.h和SAPI.c包含的函式是抽象基類的宣告和定義,各個伺服器用的SAPI模式,則是繼承了這個基類,並重新定義基類方法的子類。

總結

PHP的SAPI是Zend引擎提供的一組標準互動介面,通過註冊初始化、析構、輸入、輸出等介面,我們可以將應用程式執行在Zend引擎上,也可以把PHP嵌入到類似Apache的Web Server中。PHP常見的SAPI模式有五種,CGI/CLI模式、多程序模式、多執行緒模式、FastCGI模式和內嵌模式。

瞭解PHP的SAPI機制意義重大,幫助我們理解PHP的生命週期,並瞭解如何更好的通過C/C++為PHP編寫擴充套件,並在生命週期中找到提高系統性能的方式。