理解PHP执行流程是开发扩展、排查性能问题的基础。本文以Apache + mod_php为例,梳理从Web请求进入到Zend引擎启动、扩展加载的关键路径。
PHP通过SAPI(Server Abstraction API)与Web服务器交互。常见SAPI:
mod_php5.so)编译PHP时生成mod_php5.so,Apache启动时加载该模块并注册Hook。请求到达.php文件时,控制权移交SAPI。
php_init_handler()(sapi/apache/mod_php5.c)tsrm_startup()(线程安全资源管理器)sapi_startup(&apache_sapi_module)php_apache_startup(&apache_sapi_module)sapi_startup()(main/sapi.c)sapi_globals_struct,保存请求基本信息(Headers、编码等)。核心步骤:sapi_globals_ctor():初始化known_post_content_types HashTable(大小5)php_setup_sapi_content_types():注册默认POST内容类型virtual_cwd_startup()、reentrancy_startup()php_apache_startup() → php_module_startup()(main/main.c)zend_utility_functions(错误处理、输出、流操作函数指针)php_init_config())zend_register_standard_ini_entries(),如array、mysql等)php_ini_register_extensions())zend_startup_extensions())php_init_config()遍历extension=xxx指令,将模块路径加入php_extension_lists结构(engine为Zend扩展,functions为PHP扩展)。
void php_ini_register_extensions(TSRMLS_D) {
zend_llist_apply(&extension_lists.engine, php_load_zend_extension_cb TSRMLS_CC);
zend_llist_apply(&extension_lists.functions, php_load_php_extension_cb TSRMLS_CC);
zend_llist_destroy(&extension_lists.engine);
zend_llist_destroy(&extension_lists.functions);
}
遍历链表调用回调函数加载扩展。
php_load_php_extension_cbstatic void php_load_php_extension_cb(void *arg TSRMLS_DC) {
zend_load_extension(*((char **) arg));
}
最终调用zend_load_extension()(ext/standard/dl.c),通过dlopen加载共享库,获取get_module函数指针,调用它得到zend_module_entry。
if (zend_hash_add(&module_registry, lcname, name_len+1, (void*)module, sizeof(zend_module_entry), (void**)&module_ptr) == FAILURE) {
zend_error(E_CORE_WARNING, "Module '%s' already loaded", module->name);
return NULL;
}
module_registry是全局扩展注册表,键为小写扩展名。
zend_startup_modules(TSRMLS_C); // 依赖排序、检测注册 zend_startup_extensions(); // 逐个调用 extension->startup()
此阶段执行各扩展的PHP_MINIT_FUNCTION,完成资源分配、INI注册、类/函数注册等。
Apache请求
↓
mod_php5.so Hook
↓
php_init_handler()
├─ sapi_startup() → sapi_globals初始化
└─ php_apache_startup() → php_module_startup()
├─ zend_utility_functions 初始化
├─ php_init_config() → 解析php.ini、收集extension列表
├─ zend_register_standard_ini_entries() → 内置扩展
├─ php_ini_register_extensions() → 外部扩展
│ ├─ zend_load_extension() → dlopen、获取module_entry
│ └─ zend_hash_add(&module_registry) → 注册
├─ zend_startup_modules() → 依赖排序
└─ zend_startup_extensions() → 调用MINIT
掌握此流程,可定位扩展加载失败、INI不生效、启动顺序依赖等疑难问题。