目錄
- 1、PHP的運(yùn)行模式
- 2、一切的開始: SAPI接口
- 啟動(dòng)apache
- 請求處理初始化
- 執(zhí)行php代碼
- 腳本結(jié)束
- sapi關(guān)閉
- 3、PHP的開始和結(jié)束階段
- 4、單進(jìn)程SAPI生命周期
- 5、多進(jìn)程SAPI生命周期
- 6、多線程的SAPI生命周期
- 7、Apache一般使用多進(jìn)程模式prefork
1、PHP的運(yùn)行模式
PHP兩種運(yùn)行模式是WEB模式、CLI模式。無論哪種模式,PHP工作原理都是一樣的,作為一種SAPI運(yùn)行。
1、當(dāng)我們在終端敲入php這個(gè)命令的時(shí)候,它使用的是CLI。
它就像一個(gè)web服務(wù)器一樣來支持php完成這個(gè)請求,請求完成后再重新把控制權(quán)交給終端。
2、當(dāng)使用Apache或者別web服務(wù)器作為宿主時(shí),當(dāng)一個(gè)請求到來時(shí),PHP會(huì)來支持完成這個(gè)請求。一般有:
- 多進(jìn)程(通常編譯為apache的模塊來處理PHP請求)
- 多線程模式
2、一切的開始: SAPI接口
通常我們編寫php Web程序都是通過Apache或者Nginx這類Web服務(wù)器來測試腳本. 或者在命令行下通過php程序來執(zhí)行PHP腳本. 執(zhí)行完成腳本后,服務(wù)器應(yīng)答,瀏覽器顯示應(yīng)答信息,或者在命令結(jié)束后在標(biāo)準(zhǔn)輸出顯示內(nèi)容. 我們很少關(guān)心PHP解釋器在哪里. 雖然通過Web服務(wù)器和命令行程序執(zhí)行腳本看起來很不一樣. 實(shí)際上她們的工作是一樣的. 命令行程序和Web程序類似, 命令行參數(shù)傳遞給要執(zhí)行的腳本,相當(dāng)于通過url 請求一個(gè)PHP頁面. 腳本戳里完成后返回響應(yīng)結(jié)果,只不過命令行響應(yīng)的結(jié)果是顯示在終端上. 腳本執(zhí)行的開始都是通過SAPI接口進(jìn)行的.
啟動(dòng)apache
當(dāng)給定的SAPI啟動(dòng)時(shí),例如在對/usr/local/apache/bin/apachectl start的響應(yīng)中,PHP由初始化其內(nèi)核子系統(tǒng)開始。在接近啟動(dòng)例程的末尾,它加載每個(gè)擴(kuò)展的代碼并調(diào)用其模塊初始化例程(MINIT)。這使得每個(gè)擴(kuò)展可以初始化內(nèi)部變量、分配資源、注冊資源處理器,以及向ZE注冊自己的函數(shù),以便于腳本調(diào)用這其中的函數(shù)時(shí)候ZE知道執(zhí)行哪些代碼。
請求處理初始化
接下來,PHP等待SAPI層請求要處理的頁面。對于CGI或CLI等SAPI,這將立刻發(fā)生且只發(fā)生一次。對于Apache、IIS或其他成熟的web服務(wù)器SAPI,每次遠(yuǎn)程用戶請求頁面時(shí)都將發(fā)生,因此重復(fù)很多次,也可能并發(fā)。不管請求如何產(chǎn)生,PHP開始于要求ZE建立腳本的運(yùn)行環(huán)境,然后調(diào)用每個(gè)擴(kuò)展的請求初始化 (RINIT)函數(shù)。RINIT使得擴(kuò)展有機(jī)會(huì)設(shè)定特定的環(huán)境變量,根據(jù)請求分配資源,或者執(zhí)行其他任務(wù),如審核。 session擴(kuò)展中有個(gè)RINIT作用的典型示例,如果啟用了session.auto_start選項(xiàng),RINIT將自動(dòng)觸發(fā)用戶空間的session_start()函數(shù)以及預(yù)組裝$_SESSION變量。
執(zhí)行php代碼
一旦請求被初始化了,ZE開始接管控制權(quán),將PHP腳本翻譯成符號(hào),最終形成操作碼并逐步運(yùn)行之。如任一操作碼需要調(diào)用擴(kuò)展的函數(shù),ZE將會(huì)把參數(shù)綁定到該函數(shù),并且臨時(shí)交出控制權(quán)直到函數(shù)運(yùn)行結(jié)束。
腳本結(jié)束
腳本運(yùn)行結(jié)束后,PHP調(diào)用每個(gè)擴(kuò)展的請求關(guān)閉(RSHUTDOWN)函數(shù)以執(zhí)行最后的清理工作(如將session變量存入磁盤)。接下來,ZE執(zhí)行清理過程(垃圾收集)-有效地對之前的請求期間用到的每個(gè)變量執(zhí)行unset()。
sapi關(guān)閉
一旦完成,PHP繼續(xù)等待SAPI的其他文檔請求或者是關(guān)閉信號(hào)。對于CGI和CLI等SAPI,沒有“下一個(gè)請求”,所以SAPI立刻開始關(guān)閉。關(guān)閉期間,PHP再次遍歷每個(gè)擴(kuò)展,調(diào)用其模塊關(guān)閉(MSHUTDOWN)函數(shù),并最終關(guān)閉自己的內(nèi)核子系統(tǒng)。
簡要的過程如下:
1. PHP是隨著Apache的啟動(dòng)而運(yùn)行的;
2. PHP通過mod_php5.so模塊和Apache相連(具體說來是SAPI,即服務(wù)器應(yīng)用程序編程接口);
3. PHP總共有三個(gè)模塊:內(nèi)核、Zend引擎、以及擴(kuò)展層;
4. PHP內(nèi)核用來處理請求、文件流、錯(cuò)誤處理等相關(guān)操作;
5. Zend引擎(ZE)用以將源文件轉(zhuǎn)換成機(jī)器語言,然后在虛擬機(jī)上運(yùn)行它;
6. 擴(kuò)展層是一組函數(shù)、類庫和流,PHP使用它們來執(zhí)行一些特定的操作。比如,我們需要mysql擴(kuò)展來連接MySQL數(shù)據(jù)庫;
7. 當(dāng)ZE執(zhí)行程序時(shí)可能會(huì)需要連接若干擴(kuò)展,這時(shí)ZE將控制權(quán)交給擴(kuò)展,等處理完特定任務(wù)后再返還;
8. 最后,ZE將程序運(yùn)行結(jié)果返回給PHP內(nèi)核,它再將結(jié)果傳送給SAPI層,最終輸出到瀏覽器上。
3、PHP的開始和結(jié)束階段
開始階段有兩個(gè)過程:
第一個(gè)過程:apache啟動(dòng)的過程,即在任何請求到達(dá)之前就發(fā)生。是在整個(gè)SAPI生命周期內(nèi)(例如Apache啟動(dòng)以后的整個(gè)生命周期內(nèi)或者命令行程序整個(gè)執(zhí)行過程中)的開始階段(MINIT),該階段只進(jìn)行一次.。啟動(dòng)Apache后,PHP解釋程序也隨之啟動(dòng); PHP調(diào)用各個(gè)擴(kuò)展(模塊)的MINIT方法,從而使這些擴(kuò)展切換到可用狀態(tài)。看看php.ini文件里打開了哪些擴(kuò)展吧; MINIT的意思是“模塊初始化”。各個(gè)模塊都定義了一組函數(shù)、類庫等用以處理其他請求。 模塊在這個(gè)階段可以進(jìn)行一些初始化工作,例如注冊常量, 定義模塊使用的類等等.典型的的模塊回調(diào)函數(shù)MINIT方法如下:
PHP_MINIT_FUNCTION(myphpextension) { /* Initialize functions, classes etc */ }
{
// 注冊常量或者類等初始化操作
return SUCCESS;
}
第二個(gè)過程發(fā)生在請求階段,當(dāng)一個(gè)頁面請求發(fā)生時(shí).則在每次請求之前都會(huì)進(jìn)行初始化過程(RINIT請求開始).
請求到達(dá)之后,SAPI層將控制權(quán)交給PHP層,PHP初始化本次請求執(zhí)行腳本所需的環(huán)境變量,例如創(chuàng)建一個(gè)執(zhí)行環(huán)境,包括保存php運(yùn)行過程中變量名稱和變量值內(nèi)容的符號(hào)表. 以及當(dāng)前所有的函數(shù)以及類等信息的符號(hào)表.例如是Session模塊的RINIT,如果在php.ini中啟用了Session 模塊,那在調(diào)用該模塊的RINIT時(shí)就會(huì)初始化$_SESSION變量,并將相關(guān)內(nèi)容讀入; 然后PHP會(huì)調(diào)用所有模塊RINIT函數(shù),即“請求初始化”。 在這個(gè)階段各個(gè)模塊也可以執(zhí)行一些相關(guān)的操作, 模塊的RINIT函數(shù)和MINIT函數(shù)類似 ,RINIT方法可以看作是一個(gè)準(zhǔn)備過程,在程序執(zhí)行之間就會(huì)自動(dòng)啟動(dòng)。
PHP_RINIT_FUNCTION(myphpextension)
{
// 例如記錄請求開始時(shí)間
// 隨后在請求結(jié)束的時(shí)候記錄結(jié)束時(shí)間.這樣我們就能夠記錄下處理請求所花費(fèi)的時(shí)間了
return SUCCESS;
}
結(jié)束階段分為兩個(gè)環(huán)節(jié): 請求處理完后就進(jìn)入了結(jié)束階段, 一般腳本執(zhí)行到末尾或者通過調(diào)用exit()或者die()函數(shù),PHP都將進(jìn)入結(jié)束階段. 和開始階段對應(yīng),結(jié)束階段也分為兩個(gè)環(huán)節(jié),一個(gè)在請求結(jié)束后(RSHUWDOWN),一個(gè)在SAPI生命周期結(jié)束時(shí)(MSHUTDOWN).
第一個(gè)環(huán)節(jié):請求處理完后結(jié)束階段:請求處理完后就進(jìn)入了結(jié)束階段,PHP就會(huì)啟動(dòng)清理程序。它會(huì)按順序調(diào)用各個(gè)模塊的RSHUTDOWN方法。 RSHUTDOWN用以清除程序運(yùn)行時(shí)產(chǎn)生的符號(hào)表,也就是對每個(gè)變量調(diào)用unset函數(shù)。典型的RSHUTDOWN方法如下:
PHP_RSHUTDOWN_FUNCTION(myphpextension)
{
// 例如記錄請求結(jié)束時(shí)間, 并把相應(yīng)的信息寫入到日至文件中.
return SUCCESS;
}
第二個(gè)環(huán)節(jié):最后,所有的請求都已處理完畢,SAPI也準(zhǔn)備關(guān)閉了, PHP調(diào)用每個(gè)擴(kuò)展的MSHUTDOWN方法,這是各個(gè)模塊最后一次釋放內(nèi)存的機(jī)會(huì)。(這個(gè)是對于CGI和CLI等SAPI,沒有“下一個(gè)請求”,所以SAPI立刻開始關(guān)閉。)
典型的RSHUTDOWN方法如下:
PHP_MSHUTDOWN_FUNCTION(extension_name) {
/* Free handlers and persistent memory etc */
return SUCCESS;
}
這樣,整個(gè)PHP生命周期就結(jié)束了。要注意的是,只有在服務(wù)器沒有請求的情況下才會(huì)執(zhí)行“啟動(dòng)第一步”和“關(guān)閉第二步”。
SAPI運(yùn)行PHP都經(jīng)過下面幾個(gè)階段:
1、模塊初始化階段(Module init) :
即調(diào)用每個(gè)拓展源碼中的的PHP_MINIT_FUNCTION中的方法初始化模塊,進(jìn)行一些模塊所需變量的申請,內(nèi)存分配等。
2、請求初始化階段(Request init) :
即接受到客戶端的請求后調(diào)用每個(gè)拓展的PHP_RINIT_FUNCTION中的方法,初始化PHP腳本的執(zhí)行環(huán)境。
3、執(zhí)行PHP腳本
4、請求結(jié)束(Request Shutdown) :
這時(shí)候調(diào)用每個(gè)拓展的PHP_RSHUTDOWN_FUNCTION方法清理請求現(xiàn)場,并且ZE開始回收變量和內(nèi)存。
5、關(guān)閉模塊(Module shutdown) :
Web服務(wù)器退出或者命令行腳本執(zhí)行完畢退出會(huì)調(diào)用拓展源碼中的PHP_MSHUTDOWN_FUNCTION 方法
4、單進(jìn)程SAPI生命周期
CLI/CGI模式的PHP屬于單進(jìn)程的SAPI模式。這類的請求在處理一次請求后就關(guān)閉。也就是只會(huì)經(jīng)過如下幾個(gè)環(huán)節(jié): 開始 - 請求開始 - 請求關(guān)閉 - 結(jié)束 SAPI接口實(shí)現(xiàn)就完成了其生命周期。如圖所示:
5、多進(jìn)程SAPI生命周期
通常PHP是編譯為apache的一個(gè)模塊來處理PHP請求。Apache一般會(huì)采用多進(jìn)程模式, Apache啟動(dòng)后會(huì)
fork出多個(gè)子進(jìn)程,每個(gè)進(jìn)程的內(nèi)存空間獨(dú)立,每個(gè)子進(jìn)程都會(huì)經(jīng)過開始和結(jié)束環(huán)節(jié), 不過每個(gè)進(jìn)程的開始階
段只在進(jìn)程fork出來以來后進(jìn)行,在整個(gè)進(jìn)程的生命周期內(nèi)可能會(huì)處理多個(gè)請求。 只有在Apache關(guān)閉或者進(jìn)程
被結(jié)束之后才會(huì)進(jìn)行關(guān)閉階段,在這兩個(gè)階段之間會(huì)隨著每個(gè)請求重復(fù)請求開始-請求關(guān)閉的環(huán)節(jié)。
如圖所示:
6、多線程的SAPI生命周期
多線程模式和多進(jìn)程中的某個(gè)進(jìn)程類似,不同的是在整個(gè)進(jìn)程的生命周期內(nèi)會(huì)并行的重復(fù)著 請求開始-請求關(guān)閉的環(huán)節(jié)
在這種模式下,只有一個(gè)服務(wù)器進(jìn)程在運(yùn)行著,但會(huì)同時(shí)運(yùn)行很多線程,這樣可以減少一些資源開銷,向Module init和Module shutdown就只需要運(yùn)行一遍就行了,一些全局變量也只需要初始化一次,因?yàn)榫€程獨(dú)具的特質(zhì),使得各個(gè)請求之間方便的共享一些數(shù)據(jù)成為可能。
多線程工作方式如下圖
7、Apache一般使用多進(jìn)程模式prefork
在linux下使用#http –l 命令可以查看當(dāng)前使用的工作模式。也可以使用#apachectl -l命令。
看到的prefork.c,說明使用的prefork工作模式。
prefork 進(jìn)程池模型,用在 UNIX 和類似的系統(tǒng)上比較多,主要是由于寫起來方便,也容易移植,還不容易出問題。要知道,如果采用線程模型的話,用戶線程、內(nèi)核線程和混合型線程有不同的特性,移植起來就麻煩。prefork 模型,即預(yù)先 fork() 出來一些子進(jìn)程緩沖一下,用一個(gè)鎖來控制同步,連接到來了就放行一個(gè)子進(jìn)程,讓它去處理。
prefork MPM 使用多個(gè)子進(jìn)程,每個(gè)子進(jìn)程只有一個(gè)線程。每個(gè)進(jìn)程在某個(gè)確定的時(shí)間只能維持一個(gè)連接。在大多數(shù)平臺(tái)上,Prefork MPM在效率上要比Worker MPM要高,但是內(nèi)存使用大得多。prefork的無線程設(shè)計(jì)在某些情況下將比worker更有優(yōu)勢:他能夠使用那些沒有處理好線程安全的第三方模塊,并 且對于那些線程調(diào)試?yán)щy的平臺(tái)而言,他也更容易調(diào)試一些。
以上就是深入理解php底層之php生命周期的詳細(xì)內(nèi)容,更多關(guān)于php生命周期的資料請關(guān)注腳本之家其它相關(guān)文章!
您可能感興趣的文章:- thinkPHP5.0框架應(yīng)用請求生命周期分析
- 關(guān)于PHP5 Session生命周期介紹
- 深入解析PHP底層機(jī)制及相關(guān)原理
- PHP底層運(yùn)行機(jī)制與工作原理詳解
- PHP7數(shù)組的底層實(shí)現(xiàn)示例
- PHP的運(yùn)行機(jī)制與原理(底層)
- php7中停止php-fpm服務(wù)的方法詳解
- PHP 對接美團(tuán)大眾點(diǎn)評(píng)團(tuán)購券(門票)的開發(fā)步驟
- PHP小程序后臺(tái)部署運(yùn)行 LNMP+WNMP的方法
- 為PHP模塊添加SQL SERVER2012數(shù)據(jù)庫的步驟詳解
- php微信小程序解包過程實(shí)例詳解