大家都知道,php需要在具體的web伺服器中才能執行,例如nginx、apache等,但是php是怎樣啟動,又是怎樣在伺服器中執行,然後兩者又是怎樣進行互動的呢?
1.web伺服器呼叫php介面
以apache伺服器為例,我們看看該伺服器是怎樣啟動php,並呼叫php中的方法。apache伺服器啟動並執行php時,一般是通過mod_php7模組的形式整合(如果是php5.*版本,就是mod_php5模組,模組字尾名根據php版本而定),mod_php7的結構如下(原始碼路徑為php/sapi/apache2handler/mod_php7.c):
ap_module_declare_data module php7_module = ;
當apache需要呼叫php中的方法時,只需要將該請求通過mod_php7模組傳達給php,php層處理完後將資料返回給apache,整個過程就結束了(補充一下:apache伺服器啟動php時,其實有兩種載入方式,一種為靜態載入,一種為動態載入,剛才討論的mod_php5模組載入方式可以理解為靜態載入,也就是需要重新啟動apache伺服器,才能將php載入進去;動態載入不需要重啟伺服器,只需要通過傳送訊號的方式將php固定的模組載入到伺服器,以達到php啟動的目的,但是在進行動態載入前,需要將載入模組編譯成動態鏈結庫,然後將其配置到伺服器的配置檔案中)。上面已經給出apache在php中的model結構,下面給出apache伺服器中對應的module結構,如下(該源**在apache中,下同):
struct module_struct
可以看得出php7_module和module_struct還是有很大不同,不過如果看到php7_module.standard20_module_stuff這個巨集的定義方式,你可能就會覺得這兩個結構體很像,其實這個巨集定義了module_struct中的前8個引數,定義如下:
#define standard20_module_stuff module_magic_number_major, \
module_magic_number_minor, \
-1, \
__file__, \
null, \
null, \
module_magic_cookie, \
null /* rewrite args spot */
然後php7_module.php_dir_cmds定義了模組的所有指令集合,具體定義內容如下(**路徑為php/sapi/apache2handler/apache_config.c):
const command_rec php_dir_cmds =
};
也就是說,php層只給apache提供了上述5個指令,每個指令的實現原始碼也在apache_config.c檔案中,最後就剩php7_module.php_ap2_register_hook了,它定義的內容如下(**路徑為php/sapi/apache2handler/mod_php7.c):
void php_ap2_register_hook(apr_pool_t *p)
php7_module.php_ap2_register_hook函式包含4個鉤子和對應的處理函式,pre_config,pre_config、post_config和child_init是啟動鉤子,它們是在伺服器啟動時呼叫,handler鉤子是請求掛鉤,它是在伺服器請求是呼叫,通過這些鉤子,就可以通過apache伺服器啟動php。
將到這裡,想必大家已經知道web伺服器是如何啟動php,並呼叫php中的方法了哈,下面再給大家講講php是如何呼叫web伺服器介面的。
2.php呼叫web伺服器介面
在講述這個問題前,我們需要了解一下什麼是sapi。sapi其實是與伺服器抽象層之間遵守的共同約定,可以這麼簡單理解,當php需要呼叫伺服器中的方法,例如清除快取,但是清除快取的實現方法是在伺服器中實現,php層根本就不知道怎麼呼叫伺服器中的該方法,怎麼辦?這時雙方需要進行約定,然後伺服器提供一套約定後的介面給php,我們把這些與伺服器抽象層之間遵守的共同約定稱為sapi介面。
問題來了,對於伺服器apache,我們可以提供一套sapi,但是如果下次又來個其它的伺服器,或者其它的「第三方」,那麼我們是不是也要給他們提供一套單獨的sapi呢?我們聰明的php開發者肯定想到了這一點,即對所有的「第三方」提供一套通用的sapi介面,但是你可以會問,如果新的「第三方」需要的介面,你的通用sapi不支援,那怎麼辦呢,我的理解是將新的功能新增到php的通用sapi介面中,僅僅是個人見解哈,通用sapi結構如下(原始碼路徑: php/main/sapi.h):
struct _sapi_module_struct ;
該結構體變數較多,就不一一枚舉,簡要說明一下裡面的變數:startup函式是當sapi初始化時會被呼叫,shutdown函式是用來釋放sapi的資料結構和記憶體等,read_cookie 是在sapi啟用時被呼叫,然後將此函式獲取的值賦值給sg(request_info).cookie_data。那麼對於php提供的通用sapi,apache伺服器又是怎樣定製自己的介面呢?具體結構如下(原始碼路徑為php/sapi/apache2handler/sapi_apache2.c):
static sapi_module_struct apache2_sapi_module = ;
上述原始碼目錄php/sapi/apache2handler/中,目錄php/sapi下面放的都是通過sapi呼叫的「第三方」,該目錄結構如下圖所示,目錄php/sapi/apache2handler中都是與php互動的介面,sapi_apache2.c是php與apache約定的sapi介面檔案。
看到這裡,大家應該基本清楚php層是怎樣呼叫伺服器層的介面,為了鞏固上面的知識,下面舉個栗子,即在apache伺服器環境下讀取cookie:
sg(request_info).cookie_data = sapi_module.read_cookies(tsrmls_c);
對於任意乙個伺服器在載入時,我們都會指定sapi_module,apache的sapi_module是apache2_sapi_module,它的read_cookies方法的是php_apache_sapi_read_cookies函式,這樣就實現php層呼叫apache的介面,是不是很簡單呢:)
3.後記
這篇博文是我參考《深入理解php核心》一書總結的,參考的內容為第二章第二節「sapi概述」,不過我感覺該書中這部分內容講的有點繞,我重新編排了,然後提取了裡面的重點,並加入個人見解,如果在該文中有哪些講的不對的地方,希望能幫我指出來,大家共同提高哈,謝謝!
參考:
深入理解PHP原理之PHP指令碼執行原理 1
php是乙個被廣泛應用的指令碼語言,因為它的成功,所以很多時候,我們應用php的時候是不需要考慮底層到底是怎麼實現的,我相信大多數的php程式設計師是不會去考慮這一點的,在這篇文章中,我會從整個php的執行期入手,大致的介紹下各個階段,包括詞法分析 語法分析和opcode。從最初我們編寫的php指令...
深入理解PHP之require include順序
在大型的web專案中,include path是乙個模組化設計的根本中的根本 當然,現在也有很多基於autoload的設計,這個不影響本文的 但是正是因為include path,經常會讓我們遇到一些因為沒有找到正確的檔案而導致的看似 詭異 的問題.也就有了如下的疑問 include path是怎麼...
深入理解PHP原理之 echo的實現
php源 分析 echo實現詳解 原諒出處 echo,這個是php運用得最多的標記之一,算不上是函式,php手冊裡這麼寫的,因為它沒有返回值。今天好奇就去看看php的源 因為echo不是一般的函式,所以找起來比較費勁,一般的函式只要搜尋php function fun name 基本就能找著函式的實...