PHP 在 Web 伺服器環境中並非獨立執行,而是由 SAPI(Server API)層載入並管理。理解 PHP 的四階段生命週期(MINIT/MSHUTDOWN、RINIT/RSHUTDOWN),是編寫高效能擴展與排查記憶體洩漏的基礎。
SAPI 啟動
│
├─► MINIT (模組初始化) —— 所有擴展 MINIT 執行一次
│
├─► 請求循環 (每個 HTTP 請求)
│ │
│ ├─► RINIT (請求初始化) —— 每請求執行
│ │
│ ├─► 執行 PHP 腳本
│ │
│ └─► RSHUTDOWN (請求關閉) —— 每請求執行
│
└─► MSHUTDOWN (模組關閉) —— SAPI 關閉時執行一次
| 階段 | 時機 | 用途 | 持久性 |
|---|---|---|---|
| MINIT | SAPI 啟動後、首個請求前 | 註冊常數、類別、資源類型、INI 設定、流包裝器、過濾器 | 跨請求持久(整個 SAPI 生命週期) |
| MSHUTDOWN | SAPI 關閉前 | 釋放持久資源、關閉連線池、清理全域狀態 | — |
| RINIT | 每個請求開始 | 初始化請求級變數($_GET、$_POST、$_SESSION)、建立資料庫連線 | 單請求 |
| RSHUTDOWN | 每個請求結束 | 釋放請求級資源、關閉非持久連線、清理 Session 寫入 | — |
PHP_MINIT_FUNCTION(myextension) {
// 1. 執行緒安全全域變數分配 (ZTS 環境)
#ifdef ZTS
ts_allocate_id(&myextension_globals_id, sizeof(php_myextension_globals),
(ts_allocate_ctor) myextension_globals_ctor,
(ts_allocate_dtor) myextension_globals_dtor);
#else
myextension_globals_ctor(&myextension_globals TSRMLS_CC);
#endif
// 2. 註冊 INI 設定
REGISTER_INI_ENTRIES();
// 3. 註冊常數
REGISTER_LONG_CONSTANT("MYEXT_MEANING", 42, CONST_CS | CONST_PERSISTENT);
REGISTER_STRING_CONSTANT("MYEXT_FOO", "bar", CONST_CS | CONST_PERSISTENT);
// 4. 註冊資源類型 (持久/非持久)
le_myresource = zend_register_list_destructors_ex(
php_myext_myresource_dtor, NULL, "My Resource Type", module_number);
le_myresource_persist = zend_register_list_destructors_ex(
NULL, php_myext_myresource_dtor, "My Resource Type", module_number);
// 5. 註冊流過濾器
if (FAILURE == php_stream_filter_register_factory("myfilter", &php_myextension_filter_factory TSRMLS_CC))
return FAILURE;
// 6. 註冊流包裝器
if (FAILURE == php_register_url_stream_wrapper("myproto", &php_myextension_stream_wrapper TSRMLS_CC))
return FAILURE;
// 7. 註冊自動全域變數
#ifdef ZEND_ENGINE_2
if (zend_register_auto_global("_MYEXTENSION", sizeof("_MYEXTENSION")-1, NULL TSRMLS_CC) == FAILURE)
return FAILURE;
zend_auto_global_disable_jit("_MYEXTENSION", sizeof("_MYEXTENSION")-1 TSRMLS_CC);
#else
if (zend_register_auto_global("_MYEXTENSION", sizeof("_MYEXTENSION")-1 TSRMLS_CC) == FAILURE)
return FAILURE;
#endif
return SUCCESS;
}
CLI 可視為「單請求迷你 SAPI」:
die()、exit()、E_ERROR)會觸發 longjmp() 直接跳至請求結束,**略過後續程式碼**(含 free()),導致記憶體洩漏 —— 這正是 ZendMM 存在的意義。