這篇文章,研究一下PHP代碼是如何解釋和執行以及PHP指令碼啟動並執行生命週期。
概述
PHP服務的啟動。嚴格來說,PHP的相關進程是不需要手動啟動的,它是隨著Apache的啟動而啟動並執行。當然,如果有需要重啟PHP服務的情況下也是可以手動重啟PHP服務的。比如說在有開啟opcode的正式環境更新了代碼之後,需要重啟PHP以重新編譯PHP代碼。
從宏觀上來看,PHP核心的實現就是接收輸入的資料,內部做相應的處理然後輸出結果。對於PHP核心來說,我們編寫的PHP代碼就是核心接收的輸入資料,PHP核心接收代碼資料後,對我們編寫的的代碼進行代碼解析和運算執行,最後返回相應的運算結果。
然而,不同於平時的C語言代碼,要執行PHP代碼,首先需要將PHP代碼“翻譯”成機器語言來執行相應的功能。而要執行“翻譯”這個步驟,就需要PHP核心進行:詞法分析、文法分析等步驟。最後交給PHP核心的Zend Engine進行順次的執行。
詞法分析
將PHP代碼分隔成一個個的“單元”(TOKEN)
文法分析
將“單元”轉換為Zend Engine可執行檔操作
Zend Engine執行
對文法分析得到的操作進行順次的執行
一切PHP程式(CGI/CLI)的開始都是從SAPI(Server Application Programming Interface)介面開始。SAPI指的是PHP具體應用的編程介面。例如Apache的mod_php。
PHP開始執行以後會經過兩個主要的階段:處理請求之前的開始階段和請求之後的結束階段。
開始階段
PHP的整一個開始階段會經曆模組初始化和模組啟用兩個階段。
MINIT
即模組初始化階段,發生在Apache/Nginx啟動以後的整個生命週期或者命令列程式整個執行過程中,此階段只進行一次
RINIT
模組啟用,發生在要求階段。做一些初始化工作:如註冊常量、定義模組使用的類等等
模組在實現時可以通過如下宏來實現這些回呼函數:
PHP_MINIT_FUNCTION(myphpextension){//註冊常量或者類等初始化操作return SUCCESS;}PHP_RINIT_FUNCTION(myphpextension){//例如記錄請求開始時間//隨後在請求結束的時候記錄結束時間。這樣我們就能夠記錄處理請求所花費時間了return SUCCESS;}
PHP指令碼請求處理完就進入了結束階段,一般指令碼執行到末尾或者調用exit或die函數,PHP就進入結束階段。
結束階段
PHP的結束階段分為停用模組和關閉模組兩個環節。
RSHUTDOWN
停用模組(對應RINIT)
MSHUTDOWN
關閉模組(對應MINIT)
CLI/CGI模式的PHP屬於單進程的SAPI模式。意思就是說,PHP指令碼在執行一次之後就關閉掉,所有的變數和函數都不能繼續使用。即在CGI模式下,同一個php檔案的變數在其他php檔案中不能使用。
下面用一個例子看看單線程PHP的SAPI生命週期。
單線程SAPI生命週期
如:
php -f test.php
調用各個擴充的MINIT 模組初始化
請求test.php
調用各個擴充的RINIT 模組啟用
執行test.php
調用各個擴充的RSHUTDOWN 停用模組
執行完test.php後清理變數和記憶體
調用各個擴充的MSHUTDOWN 關閉模組
停止PHP執行
以上是一個簡單的執行流程,下面做一些補充。
PHP在調用每個模組的模組初始化前,會有一個初始化的過程,包括:
初始化若干全域變數
大多數情況下是將其設定為NULL。
初始化若干常量
這裡的常量是PHP自身的一些常量。
初始化Zend引擎和核心組件
這裡的初始化操作包括記憶體管理初始化、全域使用的函數指標初始化,對PHP源檔案進行詞法分析、文法分析、中間代碼執行的函數指標的賦值,初始化若干HashTable(比如函數表,常量表等等),為ini檔案解析做準備,為PHP源檔案解析做準備,註冊內建函數、標準常量、GLOBALS全域變數等
解析php.ini
讀取php.ini檔案,設定配置參數,載入zend擴充並註冊PHP擴充函數。
全域操作函數的初始化
初始化在使用者空間所使用頻率很高的一些全域變數,如:$\_GET、$\_POST、$\_FILES 等。
初始化靜態構建的模組和共用模組(MINIT)
初始化預設載入的模組。
模組初始化執行操作:
將模組註冊到登入模組列表
將每個模組中包含的函數註冊到函數表
禁用函數和類
會調用zend_disable_function函數將PHP的設定檔中的disable_functions變數代表的函數從CG(function_table)函數表中刪除。
啟用Zend引擎
使用init_compiler函數來初始化編譯器。
啟用SAPI
使用sapi_activate函數來初始化SG(sapi_headers)和SG(request_info),並且針對HTTP請求的方法設定一些內容。
環境初始化
初始化在使用者控制項需要用到的一些環境變數。包括伺服器環境、請求資料環境等。
模組請求初始化
PHP調用zend_activate_modules函數遍曆註冊在module_registry變數中的所有模組,調用其RINIT方法方法實現模組的請求初始化操作。
在處理了檔案相關的內容後,PHP會調用php_request_startup做請求初始化操作:
啟用Zend引擎
啟用SAPI
環境初始化
模組請求初始化
代碼的運行
以上所有準備工作完成後,就開始執行PHP程式。PHP通過zend_compile_file做詞法分析、文法分析和中間代碼產生操作,返回此檔案的所有中間代碼。如果解析的檔案有產生有效中間代碼,則調用zend_excute執行中間代碼。。如果在執行過程中出現異常並且使用者有定義對這些異常的處理,則調用這些異常處理函數。在所有的操作都處理完後,PHP通過EG(return_value_ptr_ptr)返回結果。
DEACTIVATION(關閉請求)
PHP關閉請求的過程是一個若干個關閉操作的集合,這個集合存在於php_request_shutdown函數中。這個包括:
調用所有通過register_shutdown_function()註冊的函數。這些在關閉時調用的函數是在使用者空間添加進來的。
執行所有可用的__destruct函數。這裡的解構函式包括在對象池(EG(objects_store)中的所有對象的解構函式以及EG(symbol_table)中各個元素的析構方法。
將所有的輸出刷出去。
發送HTTP應答頭。
銷毀全域變數表(PG(http_globals))的變數。
通過zend_deactivate函數,關閉詞法分析器、文法分析器和中間代碼執行器。
調用每個擴充的post-RSHUTDOWN函數。只是基本每個擴充的post_deactivate_func函數指標都是NULL。
關閉SAPI,通過sapi_deactivate銷毀SG(sapi_headers)、SG(request_info)等的內容。
關閉流的封裝器、關閉流的過濾器。
關閉記憶體管理。
重新設定最大執行時間
結束
PHP結束一個進程是,會調用sapi_flush函數將最後的內容重新整理出去。然後調用zend_shutdown函數關閉Zend引擎。
參考:[http://www.php-internals.com/book/](http://www.php-internals.com/book/)
以上就介紹了PHP是怎麼啟動並執行,包括了方面的內容,希望對PHP教程有興趣的朋友有所協助。