【PHP7源碼學習】2019-04-25 PHP生命週期淺析

Grapephp

視頻傳送門:【每日學習記錄】使用錄像設備記錄天天的學習nginx


今天咱們來看下PHP的生命週期,咱們都知道PHP生命週期有五個步驟,那麼在源碼層級是怎麼去實現PHP生命週期呢?首先,咱們拋出本文的幾個問題:segmentfault

  1. php的生命週期是什麼?每一個階段作了什麼?
  2. 爲何會有FPM?
  3. cli執行代碼和請求通過fpm執行有什麼區別?

思考ing。。。。

好的,接下來咱們解釋上邊三個問題。api

1.什麼是php的生命週期?每一個階段作了什麼?

這個問題相信你們都可以回答,php的生命週期有五個步驟:數組

- php_module_startup:模塊初始化
- php_request_startup:請求初始化
- php_execute_script:執行腳本
- php_request_shutdown:請求關閉
- php_module_shutdown:模塊關閉

在執行完這個個步驟以後,就走過了PHP的一輩子,感受設計者徹底借鑑了人的一輩子去設計的生命週期,出生,成長奮鬥,結婚生子,完成理想以及老去,妙啊。
那麼,對於這五個步驟有什麼意義呢?咱們來逐個瞭解一下。咱們拿cli來舉例子(入口在sapi/cli/php.ini),咱們假設sapi的初始化等步驟已經完成,由於本文重點是PHP生命週期,着着重講解五個步驟。安全

php_module_startup

看名字就這道這個函數的做用,模塊的初始化,即調用每一個拓展源碼中的的PHP_MINIT_FUNCTION中的方法初始化模塊,進行一些模塊所需變量的申請,內存分配等。
這一步驟主要完成的工做有如下幾點:php7

- 初始化zend_utility_functions 結構.這個結構是設置zend的函數指針,好比錯誤處理函數,輸出函數,流操做函數等.
- 設置環境變量.
- 加載php.ini配置.
- 加載php內置擴展.
- 寫日誌.
- 註冊php內部函數集.
- 調用 php_ini_register_extensions,加載全部外部擴展
- 開啓全部擴展
- 一些清理操做.

咱們看一下加載php.ini配置,代碼以下:socket

/* this will read in php.ini, set up the configuration parameters,
           load zend extensions and register php function extensions
           to be loaded later */
        if (php_init_config() == FAILURE) {
            return FAILURE;
        }//   php_init_config函數會在這裏檢查全部php.ini配置,而且找到全部加載的模塊,添加到php_extension_lists結構中.
    
        /* Register PHP core ini entries */
        REGISTER_INI_ENTRIES();//展開後爲zend_register_ini_entries(ini_entries, module_number),ini_entries是PHP_INI_BEGIN/END()兩個宏生成的配置映射規則數組,一般會把這個操做放到PHP_MINIT_FUNCTION()中。
        //注意:此時php.ini已經解析到configuration_hash哈希表中,zend_register_ini_entries()將根據配置name查找這個哈希表,
        //若是找到了代表用戶在php.ini中配置了該項,
        //而後將調用此規則指定的on_modify函數進行賦值,
        此處更詳細的介紹請看[https://www.kancloud.cn/nickbai/php7/363320]
    對於其它的一些操做是怎麼實現的,你們能夠自行查看源碼。

php_request_startup

請求初始化階段, 即接受到客戶端的請求後調用每一個拓展的PHP_RINIT_FUNCTION中的方法,初始化PHP腳本的執行環境。
在此函數的實現種主要有如下幾個函數:函數

  • zend_interned_strings_activate():初始化內部字符串哈希表
  • php_output_activate():啓動php的輸出
  • zend_activate():激活Zend引擎
  • sapi_activate():激活SAPI,進行編譯器,重置gc,執行器以及詞法掃描器。
  • zend_signal_activate(),處理一些信號
  • zend_activate_modules():回調各擴展定義的request_startup鉤子函數。

php_execute_script

執行腳本階段,入口是php_execute_script()。此過程和2同樣,均在do_cli函數內完成。首先獲取真正執行的文件信息等,把要執行的文件放在included_files列表裏邊。而後會調用zend_execute_scripts()去真正執行。真正執行的時候就涉及到了編譯,執行,op_array之類的概念。編譯過程又涉及到詞法分析,語法分析和抽象語法樹(AST)等概念。執行的話會涉及到opcode的概念。這些概念在以前的文章中已經講解過具體實現,感興趣的讀者能夠自行前往。傳送門:筆記彙總oop

php_request_shutdown

請求關閉階段。在這個階段總共有16個步驟,在源碼裏有着明確的註釋,無謂就是作一些「清理」操做,咱們看下源碼怎麼作的。

EG(current_execute_data) = NULL;/*EG(current_execute_data) 指向nirvana,所以沒法在zend_executor回調函數中安全地訪問.*/
php_deactivate_ticks()//清空tick函數
1.php_call_shutdown_functions()//調用註冊了register_shutdown_function()的全部可能的shutdown函數
2.zend_call_destructors()//調用全部可能的__destruct() 函數
3.php_output_discard_all()/php_output_end_all()://刷新全部輸出緩衝區
4.zend_unset_timeout()//重置max_execution_time(響應發送後再也不執行php代碼)
5.zend_deactivate_modules()//調用全部擴展RSHUTDOWN函數
6.php_output_deactivate()//關閉輸出層(發送設置好的HTTP頭文件,清除輸出處理程序等)
7.php_free_shutdown_functions()//釋放shutdown函數
8.zval_ptr_dtor()//銷燬 super-globals
9.php_free_request_globals()//釋放request-bound globals
10.zend_deactivate()//關閉掃描儀/執行器/編譯器並還原ini條目
11.zend_post_deactivate_modules//調用rshutdown後的全部擴展
12.sapi_deactivate//SAPI相關的shutdown (free stuff)
13.virtual_cwd_deactivate//釋放virtual CWD 內存
14.php_shutdown_stream_hashes//破壞流哈希表
15.zend_interned_strings_deactivate()/shutdown_memory_manager():Free Willy (here be crashes)
16.zend_unset_timeout():重置max_execution_time

php_module_shutdown

模塊關閉階段:與模塊初始化階段相反,這個階段將清理資源、各php模塊關閉等操做。具體的代碼函數調用再也不贅述。

2. 爲何會有FPM?

咱們在看過cli下生命週期的五個階段以後會發現一個問題,這種形式好像有個問題,就是它每來一次請求就會有這五個階段,這樣會形成多大的資源浪費啊。那麼爲了解決這個問題,FPM應運而生,FPM(FastCGI Process Manager)是 PHP FastCGI 運行模式的一個進程管理器。
歸納來講,fpm的實現就是建立一個 master進程,在master進程中建立並監聽socket,而後fork 出多個子進程,這些子進程各自accept請求,子進程的處理很是簡單,它在啓動後阻塞在accept上,有請求到達後開始讀取請求數據,讀取完成後開始處理而後再返回,在這期間是不會接收其它請求的,也就是說fpm的子進程同時只能響應一個請求,只有把這個請求處理完成後纔會accept下一個請求,這一點與nginx的事件驅動有很大的區別nginx的子進程經過epoll管理套接字,若是一個請求數據還未發送完成則會處理下一個請求,即一個進程會同時鏈接多個請求,它是非阻塞的模型,只處理活躍的套接字。
知道它的工做機制咱們就能夠想象一下他會如何去改善cli模式下每一個請求都完成一次初始化的問題,咱們猜想一下,他會在master進程進行一次初始化以後在請求階段循環,直至結束,這樣就達到了不用屢次初始化的目的。好的咱們看下它是怎麼實現的?
首先進行fpm_init,此步主要是對fpm進行初始化,加載fpm配置文件,分配用於和worker進行通訊的共享內存,建立worker_pool的套接字,啓動 master 的事件管理器(fpm 實現了一個事件管理器用於管理 IO、定時事件,其中 IO 事件經過 kqueue、epoll、poll、select 等管理,定時事件就是定時器,必定時間後觸發某個事件)等等操做。
接下來就是fpm_run的過程,master將fork出worker進程,worker進程返回main()中繼續向下執行,後面的流程就是worker進程不斷accept請求,而後執行PHP腳本並返回。fpm_run總體流程以下:

1. 等待請求:worker進程阻塞在fcgi_accept_request() 等待請求;
2. 解析請求:fastcgi請求到達後被worker接收,而後開始接收並解析請求數據,直到request數據徹底到達;
3. 請求初始化:執行php_request_startup(),此階段會調用每一個擴展的:PHP_RINIT_FUNCTION();
4. 編譯、執行:由php_execute_script() 完成 PHP 腳本的編譯、執行;
5. 關閉請求:請求完成後執行php_request_shutdown(),此階段會調用每一個擴展的:PHP_RSHUTDOWN_FUNCTION(),而後進入步驟 (1) 等待下一個請求;

在這個階段,master進程將進入fpm_event_loop()來依賴註冊的幾個事件進行不一樣的操做。
到此,對於fpm的簡單敘述就到此爲止了。能夠理解fpm的誕生就是一劑靈丹妙藥,拉長了PHP的生命戰線

3. cli執行代碼和請求通過fpm執行有什麼區別?

其實我以爲這個問題在看過上邊兩個問題以後答案就已經出來了~,那麼這塊就讓聰明的你來解決啦。

相關文章
相關標籤/搜索