深入 PHP 核心原始碼,從 dl() 函式追蹤動態擴展(.so)的載入、註冊、初始化完整流程,釐清 ZEND 模組機制關鍵節點。

./configure --enable-extname 重新編譯 PHP 二進制。.so,透過 php.ini 的 extension=extname.so 或程式碼 dl('extname.so') 載入。原始碼位置:PHP_SRC_HOME/ext/standard/dl.c
PHPAPI PHP_FUNCTION(dl) {
// ... 參數解析 ...
php_dl(filename, MODULE_TEMPORARY, return_value, 0 TSRMLS_CC);
// ...
}
PHPAPI void php_dl(char *file, int type, zval *return_value, int start_now TSRMLS_DC) {
if (php_load_extension(file, type, start_now TSRMLS_CC) == FAILURE) {
// 錯誤處理
}
}
PHPAPI int php_load_extension(char *filename, int type, int start_now TSRMLS_DC) {
// 1. 解析檔案路徑
// 2. 載入動態連結庫
handle = DL_LOAD(libpath);
// 3. 取得模組入口函式 get_module()
get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "get_module");
// 4. 呼叫 get_module() 取得 zend_module_entry 結構
module_entry = get_module();
// 5. 註冊模組到 Zend 引擎
if ((module_entry = zend_register_module_ex(module_entry TSRMLS_CC)) == NULL) {
// 錯誤處理
}
// 6. 模組啟動 (MINIT)
if ((type == MODULE_TEMPORARY || start_now)
&& zend_startup_module_ex(module_entry TSRMLS_CC) == FAILURE) {
// 錯誤處理
}
// 7. 請求啟動 (RINIT)
if ((type == MODULE_TEMPORARY || start_now)
&& module_entry->request_startup_func) {
// 錯誤處理
}
return SUCCESS;
}
| 階段 | 函式/宏 | 說明 |
|---|---|---|
| 載入 .so | DL_LOAD() | 系統動態連結器載入共享庫 |
| 取得入口 | DL_FETCH_SYMBOL("get_module") | 每個擴展必須導出的 C 函式 |
| 模組註冊 | zend_register_module_ex() | 將 zend_module_entry 註冊到模組表 |
| 模組啟動 | zend_startup_module_ex() → PHP_MINIT_FUNCTION | 常數註冊、資源類型註冊、INI 設定等 |
| 請求啟動 | request_startup_func → PHP_RINIT_FUNCTION | 每請求初始化(如 Session、資料庫連線) |
get_module() 必須正確導出 zend_module_entry。PHP_MINIT_FUNCTION 僅在模組首次載入執行一次(跨請求持久)。PHP_RINIT_FUNCTION 每個 HTTP 請求皆執行一次。dl())的擴展屬 MODULE_TEMPORARY,請求結束會自動卸載(呼叫 MSHUTDOWN/RSHUTDOWN)。