【PHP】一次請求過程的解析

1、概要

PHP-FPM啓動後,master進程會陷入event_loop(0)中來管理維持worker進程,而fork出的worker進程會回到主函數開始循環接收、處理請求。一次請求能夠總結爲 請求接收、請求處理、請求結束 三個階段,下面就詳細來說一下。html

運行環境: Mac 10.14.2 + PHP 7.3.7git

2、請求接收階段

  1. 對listen_socket加鎖: 由於accept()會有驚羣問題,在調用accept()以前會對listen_socket加鎖。驚羣問題在Linux2.6版本中獲得解決,內核在收到一個客戶端鏈接時只會喚醒等待隊列上的第一個進程。
  2. 獲取client_socket: worker進程會調用 accept(listen_socket, (struct sockaddr *)&sa, &len) 從全鏈接隊列中接受一個鏈接,若是隊列中暫時沒有則會一直阻塞着,這裏的listen_socket是在fcgi_listen()中建立監聽的。
  3. 判斷client_socket是否被容許: 知足以下請求之一便可
    1. client_socket爲unix_socket,代表客戶端爲本機
    2. 客戶端地址在allowed_clients列表裏,allowed_clients是經過listen.allowed_clients參數配置
  4. 等待client_socket上的可讀事件發生: 在do-while循環中調用poll()來監聽client_socket上的可讀事件,這裏的while條件是while (ret < 0 && errno == EINTR); EINTR錯誤是當阻塞中的poll()被捕獲到的信號中斷所產生的錯誤,因此能夠從新執行poll系統調用。
  5. 讀取client_socket中的數據: 這裏是對FastCGI協議的一個實現,Nginx會按照FastCGI協議的消息格式發送數據,worker進程再按照協議屢次read()數據並解析,消息傳遞大體以下。關於PHP如何實現FastCGI協議能夠看下這篇文章

FastCGI消息傳遞

3、請求處理階段

初始化

在上一階段讀取到請求數據後,worker進程接着會初始化輸出相關的堆棧、初始化編譯階段用到的compiler_globals(CG宏)、執行階段用到的executor_globals(EG宏)、執行每一個擴展的PHP_RINIT_FUNCTION函數 等等。github

ZendVM

講到請求處理階段就不得不提ZendVM,你們都知道PHP是解釋型語言,ZendVM就是PHP的解釋器,負責PHP的解析、執行。計算機理解不了PHP代碼,可是ZendVM能夠,對PHP而言,ZendVM就像是真正的「計算機「,這臺「計算機「能夠識別的指令就是本身事先定義好的opcode。在運行時,PHP會被編譯爲一系列opcode指令,ZendVM會逐個調用opcode對應的機器指令,最終完成PHP代碼的運行。segmentfault

ZendVM運行過程

  1. 詞法語法分析,生成AST: 這一步的目的是生成抽象語法樹AST,AST是PHP7引入的概念,PHP7以前是在語法分析後就直接生成opcode了。在這過程當中語法分析器yacc不斷調用詞法分析器re2c將PHP代碼切割爲token,而後yacc根據token組合匹配語法規則,最終生成AST。
  2. 解析AST,生成zend_op_array:這一步的目的是生成zend_op_arrayzend_op_array是編譯後全部opline指令的集合,也包括編譯期間生成的關鍵數據。對於ZendVM而言,zend_op_array就是可執行數據。
  3. ZendVM執行zend_op_arrayzend_op_array做爲ZendVM編譯器的輸出,也是ZendVM執行器的輸入。執行時,ZendVM執行器會調用opcode相應的handler完成指令的處理,其中handler是每條opcode對應的C語言編寫的處理邏輯。

4、請求結束階段

  1. 執行用戶經過register_shutdown_function()註冊的關閉函數
  2. 釋放資源,清理符號表,銷燬超全局變量,重置max_execution_time 等等
  3. 沖刷掉全部緩衝區
  4. 執行每一個擴展的PHP_RSHUTDOWN_FUNCTION函數
  5. …...

通過以上的清理操做,worker進程就準備好接收處理下一個請求了。socket

相關文章
相關標籤/搜索