深入理解PHP內(nèi)核(二)之SAPI探究
在上篇文章給大家介紹了深入了解PHP內(nèi)核(一),相信大家通過本文多多少少都學(xué)到些知識吧,關(guān)于php內(nèi)核知識繼續(xù)關(guān)注本篇文章。
SAPI是Server Application Programming Interface(服務(wù)器應(yīng)用編程接口)的縮寫。PHP通過SAPI提供了一組接口,供應(yīng)用和PHP內(nèi)核之間進(jìn)行數(shù)據(jù)交互。
簡單的講,就像函數(shù)的輸入和輸出一樣,我們通過Linux命令行執(zhí)行一段PHP代碼,本質(zhì)是Linux的Shell通過PHP的SAPI傳入一組參數(shù),Zend引擎執(zhí)行后,返回給shell,由shell顯示出來的過程。同樣的,通過Apache調(diào)用PHP,通過Web服務(wù)器給SAPI傳入數(shù)據(jù),Zend引擎執(zhí)行后,返回給Apache,由Apache顯示在頁面上。

圖1. PHP架構(gòu)圖
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(多進(jìn)程)、Multithreaded(多線程)、FastCGI和Embedded(內(nèi)嵌)。
PHP提供了一個(gè)函數(shù)查看當(dāng)前SAPI接口類型:
string php_sapi_name ( void )
PHP的運(yùn)行和加載
無論使用哪種SAPI,在PHP執(zhí)行腳本前后,都包含一系列事件:Module的Init(MINT)和Shutdown(MSHUTDOWN),Request 的Init(RINT)和Shutdown(RSHUTDOWN)。 第一階段是PHP模塊初始化階段(MINT),可以初始化擴(kuò)展內(nèi)部變量、分配資源和注冊資源處理器,在整個(gè)PHP實(shí)例生命周期內(nèi),該過程只執(zhí)行一次。
什么是PHP模塊?通過上面的PHP架構(gòu)圖,在PHP中可以使用get_loaded_extensions 函數(shù)來查看所有編譯并加載的模塊/擴(kuò)展,相當(dāng)于CLI模式下的php -m。
以PHP的Memcached擴(kuò)展源代碼為例:
PHP_MINIT_FUNCTION(memcached) {
zend_class_entry ce;
memcpy(&memcached_object_handlers,zend_get_std_object_handlers(), sizeof(zend_object_handlers));
memcached_object_handlers.clone_obj = NULL; /* 執(zhí)行了一些類似的初始化操作 */
return SUCCESS;
}
第二階段是請求初始化階段(RINT),在模塊初始化并激活后,會創(chuàng)建PHP運(yùn)行環(huán)境,同時(shí)調(diào)用所有模塊注冊的RINT函數(shù),調(diào)用每個(gè)擴(kuò)展的請求初始化函數(shù) ,設(shè)定特定的環(huán)境變量、分配資源或執(zhí)行其他任務(wù),如審核。
PHP_RINIT_FUNCTION(memcached) {
/* 執(zhí)行一些關(guān)于請求的初始化 */
return SUCCESS;
}
第三階段,請求處理完成后,會調(diào)用PHP_RSHUTDOWN_FUNCTION進(jìn)行回收,這是每個(gè)擴(kuò)展的請求關(guān)閉函數(shù),執(zhí)行最后的清理工作。Zend引擎執(zhí)行清理過程、垃圾收集、對之前的請求期間用到的每個(gè)變量執(zhí)行unset。請求完成可能是執(zhí)行到腳本完成,也可能是調(diào)用die()或exit()函數(shù)完成
第四階段,當(dāng)PHP生命周期結(jié)束時(shí)候,PHP_MSHUTDOWN_FUNCTION對模塊進(jìn)行回收處理,這是每個(gè)擴(kuò)展的模塊關(guān)閉函數(shù),用于關(guān)閉自己的內(nèi)核子系統(tǒng)。
PHP_MSHUTDOWN_FUNCTION(memcached) { /* 執(zhí)行關(guān)于模塊的銷毀工作 */ UNREGISTER_INI_ENTRIES(); return SUCCESS; }
常見的運(yùn)行模式
常見的SAPI模式有五種:
CLI和CGI模式(單進(jìn)程模式)
多進(jìn)程模式
多線程模式
FastCGI模式
嵌入式
1. CLI/CGI模式
CLI和CGI都屬于單進(jìn)程模式,PHP的生命周期在一次請求中完成。也就是說每次執(zhí)行PHP腳本,都會執(zhí)行第二部分講的四個(gè)INT和Shutdown事件。

圖2. CGI/CLI生命周期
2. 多進(jìn)程模式(Multiprocess)
多進(jìn)程模式可以將PHP內(nèi)置到Web Server中,PHP可以編譯成Apache下的prefork MPM模式和APXS模塊,當(dāng)Apache啟動后,會fork很多子進(jìn)程,每個(gè)子進(jìn)程擁有自己獨(dú)立的進(jìn)程地址空間。

圖3. 多進(jìn)程模式生命周期
在一個(gè)子進(jìn)程中,PHP的生命周期是調(diào)用MINT啟動后,執(zhí)行多次請求(RINT/RSHUTDOWN),在Apache關(guān)閉或進(jìn)程結(jié)束后,才會調(diào)用MSHUTDOWN進(jìn)行回收階段。

圖4. 多進(jìn)程的生命周期
多進(jìn)程模型中,每個(gè)子進(jìn)程都是獨(dú)立運(yùn)行,沒有代碼和數(shù)據(jù)共享,因此一個(gè)子進(jìn)程終止退出和重新生成,不會影響其他子進(jìn)程的穩(wěn)定。
3. 多線程模式(Multithreaded)
Apache2的Worker MPM采用了多線程模型,在一個(gè)進(jìn)程下創(chuàng)建多個(gè)線程,在同一個(gè)進(jìn)程地址空間執(zhí)行。

圖5. 多線程生命周期
4. FastCGI模式
在我們用的Nginx+PHP-FPM用的就是FastCGI模式,F(xiàn)astcgi是一種特殊的CGI模式,是一種常駐進(jìn)程類型的CGI,運(yùn)行后可以Fork多個(gè)進(jìn)程,不用花費(fèi)時(shí)間動態(tài)的Fork子進(jìn)程,也不需要每次請求都調(diào)用MINT/MSHUTDOWN。PHP通過PHP-FPM來管理和調(diào)度FastCGI的進(jìn)程池。Nginx和PHP-FPM通過本地的TCP Socket和Unix Socket 進(jìn)行通信。

圖6. FastCGI模式生命周期
PHP-FPM進(jìn)程管理器自身初始化,啟動多個(gè)CGI解釋器進(jìn)程等待來自Nginx的請求。當(dāng)客戶端請求達(dá)到PHP-FPM,管理器選擇到一個(gè)CGI進(jìn)程進(jìn)行處理,Nginx將CGI環(huán)境變量和標(biāo)準(zhǔn)輸入發(fā)送到一個(gè)PHP-CIG子進(jìn)程。PHP-CGI子進(jìn)程處理完成后,將標(biāo)準(zhǔn)輸出和錯(cuò)誤信息返回給Nginx,當(dāng)PHP-CGI子進(jìn)程關(guān)閉連接時(shí),請求處理完成。PHP-CGI子進(jìn)程等待著下一個(gè)連接。
可以想象CGI的系統(tǒng)開銷有多大。每一個(gè)Web 請求PHP都必須重新解析php.ini、載入全部擴(kuò)展并始化全部數(shù)據(jù)結(jié)構(gòu)。使用FastCGI,所有這些都只在進(jìn)程啟動時(shí)發(fā)生一次。另外,對于數(shù)據(jù)庫和Memcache的持續(xù)連接可以工作。
5. 內(nèi)嵌模式(Embedded)
Embed SAPI是一種特殊的SAPI,允許在C/C++語言中調(diào)用PHP提供的函數(shù)。這種SAPI和CLI模式一樣,按照Module Init => Request Init => Request => Request Shutdown => Module Shutdown的模式運(yùn)行。
Embed SAPI可以調(diào)用PHP豐富的類庫,也可以實(shí)現(xiàn)高級玩法,比如可以查看PHP的OPCODE(PHP執(zhí)行的中間碼,Zend引擎的指令,由PHP代碼生成)。
詳細(xì)請見: http://www.dbjr.com.cn/article/74641.htm
SAPI的運(yùn)行機(jī)制
我們以CGI為例,看一下SAPI的運(yùn)行機(jī)制。
static sapi_module_struct cgi_sapi_module = {
"cgi-fcgi", /* 輸出給php_info()使用 */ "CGI/FastCGI", /* pretty name */
php_cgi_startup, /* startup 當(dāng)SAPI初始化時(shí),首先會調(diào)用該函數(shù) */
php_module_shutdown_wrapper, /* shutdown 關(guān)閉函數(shù)包裝器,它用來釋放所有的SAPI的數(shù)據(jù)結(jié)構(gòu)、內(nèi)存等,調(diào)用php_module_shutdown */
sapi_cgi_activate, /* activate 此函數(shù)會在每個(gè)請求開始時(shí)調(diào)用,它會做初始化,資源分配 */
sapi_cgi_deactivate, /* deactivate 此函數(shù)會在每個(gè)請求結(jié)束時(shí)調(diào)用,它用來確保所有的數(shù)據(jù)都得到釋放 */
sapi_cgi_ub_write, /* unbuffered write 不緩存的寫操作(unbuffered write),它是用來向SAPI外部輸出數(shù)據(jù) */
sapi_cgi_flush, /* flush 刷新輸出,在CLI模式下通過使用C語言的庫函數(shù)fflush實(shí)現(xiàn)*/ NULL, /* get uid */
sapi_cgi_getenv, /* getenv 根據(jù)name查找環(huán)境變量 */
php_error, /* error handler 注冊錯(cuò)誤處理函數(shù) */
NULL, /* header handler PHP調(diào)用header()時(shí)候被調(diào)用 */
sapi_cgi_send_headers, /* send headers handler 發(fā)送頭部信息*/
NULL, /* send header handler 發(fā)送一個(gè)單獨(dú)的頭部信息 */
sapi_cgi_read_post, /* read POST data 當(dāng)請求的方法是POST時(shí),程序獲取POST數(shù)據(jù),寫入$_POST數(shù)組 */
sapi_cgi_read_cookies, /* read Cookies 獲取Cookie值 */
sapi_cgi_register_variables, /* register server variables 給$_SERVER添加環(huán)境變量 */
sapi_cgi_log_message, /* Log message 輸出錯(cuò)誤信息 */
NULL, /* Get request time */
NULL, /* Child terminate */
STANDARD_SAPI_MODULE_PROPERTIES
};
由上面代碼可見,PHP的SAPI像是面向?qū)ο笾谢悾琒API.h和SAPI.c包含的函數(shù)是抽象基類的聲明和定義,各個(gè)服務(wù)器用的SAPI模式,則是繼承了這個(gè)基類,并重新定義基類方法的子類。
總結(jié)
PHP的SAPI是Zend引擎提供的一組標(biāo)準(zhǔn)交互接口,通過注冊初始化、析構(gòu)、輸入、輸出等接口,我們可以將應(yīng)用程序運(yùn)行在Zend引擎上,也可以把PHP嵌入到類似Apache的Web Server中。PHP常見的SAPI模式有五種,CGI/CLI模式、多進(jìn)程模式、多線程模式、FastCGI模式和內(nèi)嵌模式。
了解PHP的SAPI機(jī)制意義重大,幫助我們理解PHP的生命周期,并了解如何更好的通過C/C++為PHP編寫擴(kuò)展,并在生命周期中找到提高系統(tǒng)性能的方式。
- php natsort內(nèi)核函數(shù)淺析
- php in_array 函數(shù)使用說明與in_array需要注意的地方說明
- PHP數(shù)組的交集array_intersect(),array_intersect_assoc(),array_inter_key()函數(shù)的小問題
- php array_intersect比array_diff快(附詳細(xì)的使用說明)
- PHP內(nèi)核介紹及擴(kuò)展開發(fā)指南—基礎(chǔ)知識
- php數(shù)組函數(shù)序列之in_array() 查找數(shù)組值是否存在
- php數(shù)組函數(shù)序列之a(chǎn)rray_combine() - 數(shù)組合并函數(shù)使用說明
- php數(shù)組函數(shù)序列之in_array() - 查找數(shù)組中是否存在指定值
- php數(shù)組函數(shù)序列之a(chǎn)rray_intersect() 返回兩個(gè)或多個(gè)數(shù)組的交集數(shù)組
- 使用js判斷數(shù)組中是否包含某一元素(類似于php中的in_array())
- PHP內(nèi)核探索:變量存儲與類型使用說明
- PHP內(nèi)核探索:變量概述
- php內(nèi)核解析:PHP中的哈希表
- 2個(gè)自定義的PHP in_array 函數(shù),解決大量數(shù)據(jù)判斷in_array的效率問題
- php數(shù)組查找函數(shù)in_array()、array_search()、array_key_exists()使用實(shí)例
- PHP函數(shù)in_array()使用詳解
- php提示W(wǎng)arning:mysql_fetch_array() expects的解決方法
- PHP內(nèi)核探索:哈希表碰撞攻擊原理
- 深入理解PHP內(nèi)核(一)
- 深入php內(nèi)核之php in array
相關(guān)文章
命令行執(zhí)行php腳本中的$argv和$argc配置方法
這篇文章主要介紹了命令行執(zhí)行php腳本 中$argv和$argc的方法,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2018-01-01
laravel 實(shí)現(xiàn)上傳圖片到本地和前臺訪問示例
今天小編就為大家分享一篇laravel 實(shí)現(xiàn)上傳圖片到本地和前臺訪問示例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-10-10
Laravel 5框架學(xué)習(xí)之?dāng)?shù)據(jù)庫遷移(Migrations)
本文給大家介紹的是Laravel5框架中最強(qiáng)大的功能之一數(shù)據(jù)庫遷移(database migrations),本文詳細(xì)給大家介紹數(shù)據(jù)庫遷移的步驟和方法,非常實(shí)用,有需要的小伙伴可以參考下。2015-04-04
tp5.1框架數(shù)據(jù)庫子查詢操作實(shí)例分析
這篇文章主要介紹了tp5.1框架數(shù)據(jù)庫子查詢操作,結(jié)合實(shí)例形式分析了tp5.1框架數(shù)據(jù)庫子查詢相關(guān)原理、操作實(shí)現(xiàn)方法與注意事項(xiàng),需要的朋友可以參考下2020-05-05
在phpstudy集成環(huán)境下的nginx服務(wù)器下配置url重寫
這篇文章主要介紹了在phpstudy集成環(huán)境下的nginx服務(wù)器下配置url重寫的教程,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12
PHP網(wǎng)頁安全認(rèn)證的實(shí)例詳解
這篇文章主要介紹了PHP網(wǎng)頁安全認(rèn)證的實(shí)例詳解的相關(guān)資料,這里提供了兩種實(shí)現(xiàn)方法,一種基于數(shù)據(jù)庫另一種不基于數(shù)據(jù)庫的方法,希望通過本能幫助到大家,需要的朋友可以參考下2017-09-09
深入探討:Nginx 502 Bad Gateway錯(cuò)誤的解決方法
本篇文章是對Nginx 502 Bad Gateway錯(cuò)誤的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
php curl請求接口并獲取數(shù)據(jù)的示例代碼
本篇文章主要介紹了php curl請求接口并獲取數(shù)據(jù)的示例代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-08-08
Session的工作機(jī)制詳解和安全性問題(PHP實(shí)例講解)
有一點(diǎn)我們必須承認(rèn),大多數(shù)web應(yīng)用程序都離不開session的使用。這篇文章將會結(jié)合php以及http協(xié)議來分析如何建立一個(gè)安全的會話管理機(jī)制2014-04-04

