站長資訊網
        最全最豐富的資訊網站

        探索PHP 生命周期

        探索PHP 生命周期

        學習 PHP 生命周期

        PHP的生命周期是一個很復雜的過程,其生命周期應該被熱衷于使用它的人所掌握。主要內容如下:

        PHP 啟動。如果運行的是 CLI 或者 FPM,它將運行 C main()。如果作為模塊運行到網絡服務器,像使用 apxs2 SAPI (Apache 2),則 PHP 在 Apache 啟動后不久啟動,并開始運行其模塊的啟動序列,PHP 就是其中之一。在內部稱啟動為模塊啟動步驟。我們也將其縮寫為MINIT步驟。

        一旦啟動,PHP 將等待處理一個/幾個請求。當我們談論 PHP CLI時,將只有一個請求:當前腳本要運行。但是,當我們談論 Web 環境時——應該是 PHP-FPM 或 Web 服務器模塊——PHP 可以一個接一個地處理多個請求。這完全依賴于你如何配置你的 Web 服務器:你可以告訴它處理無限數量的請求,或在關閉并回收該過程之前處理特定數量的請求。每次一個新的請求在線程中要處理時,PHP 就會運行請求啟動步驟。我們稱之為 RINIT

        相關學習推薦:PHP編程從入門到精通

        請求得到處理,(可能)生成了一些內容,OK。是時候關閉請求,并準備好處理另一個請求。關閉請求調用請求關閉步驟。我們稱之為RSHUTDOWN。·

        當處理完X個請求(一個,幾十個,數千個等),PHP 最后會自行關閉,然后結束。關閉 PHP 進程稱為模塊關閉步驟。縮寫為 MSHUTDOWN

        如果我們可以畫出這些步驟,則可能會得到以下信息:

        探索PHP 生命周期

        并行模型

        在 CLI 環境,任何事都很容易:一個進程處理一個請求:它會啟動一個單獨的 PHP 腳本,然后結束。CLI 環境是 Web 環境的一種特殊化,它更為復雜。

        為了同時處理多個請求,你必須運行并行模型。在 PHP 中存在兩種:

        • The process-based model 基于進程的模型
        • The thread-based model 基于線程的模型

        使用基于進程的模型,操作系統將每個 PHP 解釋器隔離到自己的進程中。這種模型在 Unix 非常普遍。每個請求都到它自己的進程。PHP-CLI、PHP-FPM 和 PHP-CGI 使用該模型。

        在基于線程的模型中,每個 PHP 解釋器都使用線程庫隔離到線程中。這個模型主要用在 Windows 操作系統,但也可以用在大多數的 Unix中。要求 PHP 和其擴展在 ZTS 模式下被構建。

        這是基于進程的模型:

        探索PHP 生命周期

        這是基于線程的模型:

        探索PHP 生命周期

        注意

        作為擴展開發者,PHP 的多進程模塊不是你的選擇。你將需要支持它。你必須讓你的擴展支持在線程環境中運行,特別是在 Windows平臺下,并且必須針對它編程。

        PHP 擴展鉤子

        你可能猜到了,PHP 引擎將在多個生命周期點觸發你的擴展。我們稱它們為鉤子函數。你的擴展程序可以在向引擎注冊時,通過聲明函數鉤子來聲明對特定生命周期點的興趣。
        在你分析 PHP 擴展結構時(zend_module_entry 結構),這些鉤子可以清晰地看到:

        struct _zend_module_entry {         unsigned short size;         unsigned int zend_api;         unsigned char zend_debug;         unsigned char zts;         const struct _zend_ini_entry *ini_entry;         const struct _zend_module_dep *deps;         const char *name;         const struct _zend_function_entry *functions;         int (*module_startup_func)(INIT_FUNC_ARGS);        /* MINIT() */         int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);   /* MSHUTDOWN() */         int (*request_startup_func)(INIT_FUNC_ARGS);       /* RINIT() */         int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);  /* RSHUTDOWN() */         void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);     /* PHPINFO() */         const char *version;         size_t globals_size; #ifdef ZTS         ts_rsrc_id* globals_id_ptr; #else         void* globals_ptr; #endif         void (*globals_ctor)(void *global);                /* GINIT() */         void (*globals_dtor)(void *global);                /* GSHUTDOWN */         int (*post_deactivate_func)(void);                 /* PRSHUTDOWN() */         int module_started;         unsigned char type;         void *handle;         int module_number;         const char *build_id; };

        現在讓我們看看你應該在這些鉤子中編寫哪種代碼。

        模塊初始化:MINIT()

        這是 PHP 進程啟動步驟。在擴展的MINIT()中,你將加載并分配以后每次請求需要的任何持久對象或者信息。它們的大部分將分配為只讀對象。

        MINIT()中,尚未有線程或進程彈出,所以你完全可以訪問全局變量,而沒有任何保護。另外,由于請求尚未啟動,因此你不能分配請求綁定的內存。你永遠不會在MINIT()步驟中使用Zend 內存管理 分配,但是會使用永久分配。不是 emalloc(),而是pemalloc()。否則會導致崩潰。

        MINIT()中,執行引擎仍未啟動,所以不要在沒有特別注意的情況下,嘗試訪問其任何結構。

        如果你需要為你的擴展注冊 INI 入口,則MINIT() 是正確的做法。

        如果你要為以后使用而注冊只讀zend_strings,請使用持久分配了。

        如果你需要分配的對象在處理請求時會寫入,那么你必須復制它們的內存分配到該請求的線程專用池。記住,你只可以在MINIT()中安全地寫入全局空間。

        注意

        內存管理、分配和調試是內存管理的部分章節。

        在 php_module_startup()函數中,通過zend_startup_modules()觸發 MINIT()

        模塊終止:MSHUTDOWN()

        這是 PHP 進程終止步驟。很容易, 基本上,你在這里運行了與MINIT()中使用的相反的操作。你釋放了資源,取消 INI 設置的注冊等等。

        再次注意:執行引擎是關閉的,所以你不應在此處訪問其任何變量。

        由于你在此處不需要請求,所以不應使用Zend 內存管理 的efree() 或類似函數去釋放資源,但對于釋放持久分配,使用pefree()

        在php_module_shutdown()函數中,由zend_shutdown()zend_destroy_modules()中觸發MSHUTDOWN()

        請求初始化: RINIT()

        剛剛看過的請求,PHP 將在這里處理它。在RINIT()中,你引導了處理該精確請求所需的資源。PHP 是一種無共享架構,它提供了內存管理功能。

        RINIT()中,如果需要分配動態內存,你將使用Zend 內存管理器。你將調用 emalloc()。Zend 內存管理器 追蹤你通過它分配的內存,當請求關閉時,如果你忘記這么做,它將嘗試釋放請求綁定的內存(你不應這么做)。

        在這里,你不應請求持久的動態內存,即 libc 的malloc() 或Zend 的pemalloc()。如果你在這里請求持久內存,并且忘記釋放它,則將造成泄露,并且隨著 PHP 處理越來越多的請求而堆積,最終導致進程崩潰(Kernel OOM) ,并且導致機器內存不足。

        另外,務必注意不要在這里寫入全局空間。如果 PHP 作為選定的并行模型運行到線程中,那么你將修改每個線程池中的上下文(所有與你的請求并行處理的請求),并且如果你沒有鎖定內存,也可能觸發競爭條件。如果你需要全局,你必須保護它們。

        注意

        全局范圍管理解釋在專用章節。

        在php_request_startup()函數中,通過zend_activate_module()觸發RINIT()

        請求終止: RSHUTDOWN()

        這是 PHP 請求終止步驟。PHP 剛結束處理其請求,現在來清理其部分作為無共享架構的內存。接下來的請求不應記住當前請求的任何內容。很容易,基本上,你在此處執行了與RINIT()使用的相反的操作。你釋放了請求綁定的資源。

        由于你在此處使用了請求,你應使用 Zend 內存管理器的efree()或類似方式釋放資源。如果你忘記釋放并且造成泄露,在調試版本下,內存管理器將在進程stderr上記錄關于泄露的指針的日記,并且將為你釋放它們。

        給你個主意,RSHUTDOWN()將被調用:

        • 執行用戶區關閉功能后 (register_shutdown_function())
        • 在調用每個對象析構函數之后
        • PHP 輸出緩沖區刷新之后
        • 禁用 max_execution_time 之后

        在php_request_shutdown()函數中,通過zend_deactivate_modules()觸發RSHUTDOWN()

        Post 請求終止: PRSHUTDOWN()

        這個鉤子很少使用。它在 RSHUTDOWN()之后調用,但是中間還會運行一些額外的引擎代碼。
        尤其是在 Post-RSHUTDOWN 中:

        • PHP 輸出緩沖區已關閉,并且它的處理程序已刷新
        • PHP 超全局已經銷毀
        • 執行引擎已經關閉

        這個鉤子很少使用。在php_request_shutdown()函數中,通過zend_post_deactivate_modules(),在RSHUTDOWN()之后被觸發。

        全局初始化: GINIT()

        線程庫每次彈出線程時都會調用該鉤子。如果你使用多進程,當 PHP 啟動,僅在觸發 MINIT() 之前調用此函數。

        這里不講太多細節,只需在這里簡單地初始化全局變量,通常初始化為0。全局管理將在專用章節詳細說明。

        記住,全局變量不會在每次請求后清理。如果你需要為每次新的請求重置它們(可能),那么你必須將這樣地進程放到RINIT()中。

        注意

        全局范圍管理在專用章節詳細介紹。

        全局終止: GSHUTDOWN()

        在線程庫中,每當線程終止時都會調用該鉤子。如果你使用多線程,該函數將在 PHP 終止期間(在MSHUTDOWN())被調用一次。

        在這里不提供太多細節,你只需簡單地在這里取消初始化你的全局變量,通常你不必做什么,但如果在構建全局(GINIT())時分配了資源,在這里的步驟你應該釋放它們。

        全局管理將在專用章節詳細介紹。

        記住,全局變量在每次請求后不會清除。即GSHUTDOWN()不會作為RSHUTDOWN()的一部分被調用。

        注意

        全局范圍管理在專用章節有詳細介紹。

        信息收集: MINFO()

        該鉤子很特殊,它永遠不會被引擎自動觸發,只有你詢問它有關擴展的信息時才會觸發。典型的例子是調用phpinfo()。然后運行此函數,并將有關當前擴展的特殊信息打印到流中。

        簡而言之,phpinfo() 展示信息。

        該函數也可以通過 CLI 使用反射開關之一調用,例如php --ri pib 或通過用戶區調用ini_get_all()

        你可以將其留空,在這種情況下,只有擴展的名字顯示,沒有其他(可能不會顯示 INI 設置,因為這是 MINFO() 的一部分)。

        關于 PHP 生命周期的思考

        探索PHP 生命周期

        你可能已經發現了,RINIT()RSHUTDOWN() 尤其重要,因為它們在擴展中被觸發成千上萬次。如果 PHP 步驟是關于 Web (不是 CLI),并且已經配置為可以處理無數次請求,那么你的 RINIT()/RSHUTDOWN() 組將被無數次調用。

        我們想要再次引起你對內存管理的關注。在處理請求時(在RINIT()RSHUTDOWN()之間),你最終泄露的小字節,將對滿載服務器產生嚴重影響。這就是為什么建議你使用 Zend 內存管理器 進行此類分配,并且準備好調試內存布局。作為無共享架構的一部分,PHP 在每次請求最后都會忘記并釋放請求內存,這是 PHP 的內部設計。

        另外,如果你的崩潰信號是 SIGSEGV (壞內存訪問),則整個進程會崩潰。如果 PHP 是使用線程作為多進程引擎,那么你所有其他線程也將崩潰,甚至可能造成服務器崩潰。

        注意

        C 語言不是 PHP 語言。使用 C,在程序的錯誤很可能導致程序的崩潰與終止。

        通過重寫函數指針進行掛鉤

        現在你知道引擎何時會觸發代碼,還存在值得注意的函數指針,你可以替換它們來掛載到引擎。因為那些指針是全局變量,因此你可以將它們替換為 MINIT() 步驟,并將它們放回MSHUTDOWN()中。

        感興趣的有:

        • AST, Zend/zend_ast.h:

          • void (zend_ast_process_t)(zend_ast ast)
        • Compiler, Zend/zend_compile.h:

          • zend_op_array (zend_compile_file)(zend_file_handle file_handle, int type)*
          • zend_op_array (zend_compile_string)(zval source_string, char filename)
        • Executor, Zend/zend_execute.h:

          • void (zend_execute_ex)(zend_execute_data execute_data)
          • void (zend_execute_internal)(zend_execute_data execute_data, zval return_value)*
        • GC, Zend/zend_gc.h:

          • int (gc_collect_cycles)(void)*
        • TSRM, TSRM/TSRM.h:

          • void (tsrm_thread_begin_func_t)(THREAD_T thread_id)*
          • void (tsrm_thread_end_func_t)(THREAD_T thread_id)*
        • Error, Zend/zend.h:

          • void (zend_error_cb)(int type, const char error_filename, const uint error_lineno, const char format, va_list args)*
        • Exceptions, Zend/zend_exceptions.h:

          • void (zend_throw_exception_hook)(zval ex)
        • Lifetime, Zend/zend.h:

          • void (zend_on_timeout)(int seconds)*
          • void (zend_interrupt_function)(zend_execute_data execute_data)
          • void (zend_ticks_function)(int ticks)*

        還有其他存在,但是上面的是最重要的,當你設計 PHP 擴展時,你可能需要。因為它們的名字很容易看,所以不再詳細解釋它們。

        如果你需要

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 久久精品国产亚洲av麻豆色欲| 亚洲欧美一级久久精品| 国产午夜精品理论片免费观看 | 99久久人妻无码精品系列| 久久狠狠高潮亚洲精品| 国产精品福利在线观看免费不卡| 精品国产一区AV天美传媒| 国产精品视频分类一区| 亚洲精品无码永久中文字幕| 国产精品午夜久久| 久久国产精品国产自线拍免费| 久久精品aⅴ无码中文字字幕重口 久久精品a亚洲国产v高清不卡 | 国产网红无码精品视频| 亚洲欧美精品AAAAAA片| 热久久国产欧美一区二区精品| 国产精品无码不卡一区二区三区| 免费欧美精品a在线| 99久久精品日本一区二区免费 | 日韩精品一区二区午夜成人版| 国产精品久久久久乳精品爆| 青青草国产精品欧美成人| laowang在线精品视频| 国产精品无码无片在线观看| 久久精品国产亚洲av麻豆小说| 欧美肥屁VIDEOSSEX精品| 日韩人妻精品无码一区二区三区| 宅男在线国产精品无码| 在线观看亚洲精品福利片| 亚洲精品偷拍视频免费观看| 亚洲av午夜成人片精品电影| 热综合一本伊人久久精品| 亚洲AV无码成人精品区狼人影院| 免费精品国自产拍在线播放| 日韩美女18网站久久精品| 无码国产亚洲日韩国精品视频一区二区三区 | 亚洲精品你懂的在线观看| 亚洲国产成人精品91久久久 | 国产精品亚洲一区二区三区在线 | 欧美国产精品va在线观看| 日韩精品在线播放| 一区二区三区精品高清视频免费在线播放|