PHP底層工作原理
PHP的相關(guān)進(jìn)程是隨著Web服務(wù)器如Apache、Nginx等的啟動而運(yùn)行的慨畸,這里以Apache為例簡要梳理下流程:
- PHP通過
mod_php.so
擴(kuò)展和Apache建立聯(lián)系,具體來說是SAPI
服務(wù)器應(yīng)用程序編程接口。
當(dāng)Apache服務(wù)器啟動后陨献,PHP解釋程序也隨之啟動,PHP啟動的過程分為兩步:
- 初始化環(huán)境變量懂更,這些環(huán)境變量將在
SAPI
生命周期中發(fā)生作用眨业。
PHP解釋程序啟動后,會調(diào)用各個(gè)擴(kuò)展的MINIT
方法即“模塊初始化”沮协,從而使擴(kuò)展切換到可用狀態(tài)龄捡。MINIT
指的是,每個(gè)擴(kuò)展模塊都定義了一組函數(shù)慷暂、類庫等用于處理其它請求聘殖。 - 生成只針對當(dāng)前請求的變量設(shè)置
當(dāng)客戶端的請求發(fā)生時(shí),SAPI
層將控制權(quán)交給PHP層行瑞。于是奸腺,PHP設(shè)置了用于回復(fù)本次請求所需的環(huán)境變量,用來存放執(zhí)行過程中產(chǎn)生的變量名和值血久。PHP調(diào)用各個(gè)擴(kuò)展模塊的RINIT
方法即請求初始化突照。經(jīng)典的案例使Session模塊的RINIT方法,如果在php.ini
中啟用了Session擴(kuò)展模塊氧吐,那么在調(diào)用該模塊的RINIT
時(shí)就會初始化$_SESSION
全局變量讹蘑,并將相關(guān)內(nèi)容讀取末盔。RINIT
方法可看作是一個(gè)準(zhǔn)備過程,在程序執(zhí)行之間就會自動啟動衔肢。
如同PHP啟動一樣庄岖,PHP的關(guān)閉也分為兩個(gè)步驟
- 一旦腳本執(zhí)行完畢,無論是執(zhí)行到文件末尾還是使用
exit
或die
函數(shù)中止角骤。PHP都會啟動清理程序隅忿,清理程序會按順序調(diào)用各個(gè)擴(kuò)展模塊的RSHUTDOWN
方法,RSHUTDOWN
方法用來清理程序運(yùn)行時(shí)產(chǎn)生的符號表邦尊,也就是對每個(gè)變量調(diào)用unset
函數(shù)背桐。 - 當(dāng)所有的請求都處理完畢后,
SAPI
也準(zhǔn)備關(guān)閉了蝉揍。此時(shí)链峭,PHP會調(diào)用每個(gè)擴(kuò)展模塊的MSHUTDOWN
方法,這是各個(gè)擴(kuò)展模塊最后一次釋放內(nèi)存的機(jī)會又沾。
- PHP中共有三個(gè)模塊:PHP內(nèi)核弊仪、Zend引擎、擴(kuò)展層
- PHP內(nèi)核:用來處理請求杖刷、文件流励饵、錯(cuò)誤處理等相關(guān)操作
- Zend引擎:將PHP源文件轉(zhuǎn)換成機(jī)器語言,并在Zend虛擬機(jī)上運(yùn)行滑燃。
- 擴(kuò)展層:是一組函數(shù)役听、類庫和流
PHP使用這個(gè)三個(gè)模塊來執(zhí)行特定的操作,如使用MySQL擴(kuò)展來連接MySQL數(shù)據(jù)表窘。
- 當(dāng)Zend引擎執(zhí)行程序時(shí)可能會需要連接若干擴(kuò)展典予,此時(shí)Zend引擎會將控制權(quán)交給擴(kuò)展,等處理完特定任務(wù)后再返還乐严。
- Zend引擎將運(yùn)行結(jié)果返回給PHP內(nèi)核瘤袖,PHP內(nèi)核將結(jié)果傳遞給SAPI,最終通過Web服務(wù)器輸出到瀏覽器昂验。
PHP腳本運(yùn)行流程
PHP作為Swoole的宿主孽椰,下圖是以CLI命令行下執(zhí)行一個(gè)PHP腳本文件時(shí)的完整流程:
SAPI
是PHP給外部環(huán)境執(zhí)行PHP內(nèi)核提供的統(tǒng)一接口,常見有三種:CLI凛篙、PHP-FPM黍匾、MOD_PHP。
以PHP-FPM為例呛梆,將PHP運(yùn)行周期的關(guān)鍵步驟提热裱摹:
初始化
PHP引擎初始化公用配置,讀取.ini
配置文件填物,加載zend
引擎纹腌。MINIT
執(zhí)行PHP各個(gè)擴(kuò)展模塊的MINIT
(模塊初始化)方法后霎终,常駐在PHP-FPM進(jìn)程中,等待處理請求升薯。RINIT
當(dāng)請求過來后莱褒,調(diào)用PHP各個(gè)擴(kuò)展模塊的RINIT
(請求初始化)方法進(jìn)行請求內(nèi)數(shù)據(jù)的初始化,如超全局變量和模塊數(shù)據(jù)的初始化等操作涎劈。-
執(zhí)行PHP腳本
加載PHP腳本文件广凸,進(jìn)行詞法分析、語法分析蛛枚、生成Opcode中間代碼谅海,然后交給Zend虛擬機(jī),暫存執(zhí)行結(jié)果蹦浦。
解釋型語言PHP的執(zhí)行流程 RSHUTDOWN
在結(jié)果返回給PHP-FPM之前扭吁,會調(diào)用PHP各個(gè)擴(kuò)展模塊的RSHUTDOWN
(請求關(guān)閉)方法進(jìn)行數(shù)據(jù)的回收,Zend虛擬機(jī)會關(guān)閉打開的數(shù)據(jù)流盲镶,進(jìn)行內(nèi)存釋放等操作侥袜,然后把暫存的執(zhí)行結(jié)果flush
輸出。MSHUTDOWN
當(dāng)重啟PHP-FPM時(shí)會調(diào)用PHP各個(gè)擴(kuò)展模塊的MSHUTDOWN
(模塊關(guān)閉)方法溉贿,執(zhí)行關(guān)閉Zend引擎等操作枫吧。
通過以上流程可以發(fā)現(xiàn)PHP-FPM中每個(gè)請求都是在執(zhí)行第3~5步,Opcode緩存是將第4步的詞法分析顽照、語法分析、生成Opcode中間代碼等幾個(gè)操作給緩存起來闽寡,從而達(dá)到加速的目的代兵。
既然每個(gè)請求都是獨(dú)立的,那么能不能進(jìn)行數(shù)據(jù)共享呢爷狈?由于在MINIT
模塊初始化時(shí)數(shù)據(jù)是常駐在PHP-FPM進(jìn)程中的植影,所有是可以實(shí)現(xiàn)的,例如比較典型的.ini
配置文件是放在這一步的涎永。另外每個(gè)請求都能夠獨(dú)立釋放內(nèi)存思币,總體上是安全的,但也是有問題的羡微,很有可能在擴(kuò)展層就存在內(nèi)存泄漏谷饿。所以PHP-FPM提供max_request
來重啟PHP-FPM,達(dá)到完全釋放內(nèi)存的目的妈倔。
Swoole的生命周期
分析了PHP的基本執(zhí)行流程后博投,Swoole是在哪一步執(zhí)行的呢?首先盯蝴,Swoole運(yùn)行有個(gè)前提條件:必須在CLI命令行模式下執(zhí)行毅哗。Swoole在PHP執(zhí)行流程的第4步執(zhí)行PHP腳本時(shí)就接管了PHP听怕,進(jìn)入了Swoole的生命周期。
Swoole的生命周期以多進(jìn)程模式為例虑绵,具體流程如下:
- 初始化
創(chuàng)建Manager管理進(jìn)程尿瞭,創(chuàng)建Worker工作子進(jìn)程,監(jiān)聽所有TCP/UDP端口翅睛,監(jiān)聽定時(shí)器Timer
声搁。 - onStart
onStart
回調(diào)函數(shù)是在Master主進(jìn)程中執(zhí)行的,和Worker工作子進(jìn)程的onWorkStart
是并行的宏所,并沒有先后之分酥艳。在此回調(diào)函數(shù)中強(qiáng)烈要求只做Log記錄和進(jìn)程名設(shè)置操作,不要做業(yè)務(wù)邏輯爬骤。因?yàn)闃I(yè)務(wù)邏輯代碼的錯(cuò)誤將直接導(dǎo)致Master主進(jìn)程Crash崩潰充石,進(jìn)而讓整個(gè)Swoole服務(wù)器無法對外提供服務(wù)。 - onReceive
客戶端請求的數(shù)據(jù)到達(dá)時(shí)會調(diào)用onReceive
函數(shù)霞玄,進(jìn)行業(yè)務(wù)邏輯處理輸出結(jié)果骤铃。客戶端發(fā)送的多次請求坷剧,服務(wù)端是可以一次性接收的惰爬,所以會發(fā)現(xiàn)一個(gè)問題是onReceive
接收的數(shù)據(jù)會非常大。 - onWorkerStop
Worker工作子進(jìn)程退出時(shí)回調(diào)onWorkerStop
函數(shù)惫企。 - onShutDown
Swoole服務(wù)停止時(shí)回調(diào)onShutDown
函數(shù)撕瞧,然后繼續(xù)PHP-FPM的第5~6步,最后退出PHP的生命周期狞尔。