
php 启动过程 - sapi MINIT 过程
sapi 概念
- sapi 是 php 的应用编程接口, server 端接收请求通过 sapi 接口层交给 php 处理
- 不同的 server 端底层实现不同, 相应的数据结构已经方法也有所不同, 但是对于 php 层面来说是一样的, 就是因为 sapi 层的存在
- sapi 层对不同的 server 端进行了封装, 让 php 在处理时, 采用统一的处理方法, 并不感知底层到底是什么 server 端
sapi 生命周期前的启动过程 (以 apache2 为例)
-
apache 加载 php 模块
-
httpd.conf 配置
LoadModule php5_module modules/mod_php5.so
php5_module 是模块名称
apache 在启动加载模块时会根据模块名查找并加载模块, apache 模块文件必须是 "mod_" 开头的文件, 对于 php 来说, 则是 mod_php5.c
-
apache 的每个模块均为 module 结构体
AP_MODULE_DECLARE_DATA module php5_module = {
STANDARD20_MODULE_STUFF,
create_php_config, /* create per-directory config structure */
merge_php_config, /* merge per-directory config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
// 模块指令集合
php_dir_cmds, /* command apr_table_t */
// php 模块注册钩子, 服务启动时注册
php_ap2_register_hook /* register hooks */
};php_dir_cmds 为模块指令集, 当 apache 接收到指令后, 会遍历每个模块的指令集, 查看哪个模块能处理该指令, 进而调用相应处理函数
-
php_ap2_register_hook 为模块注册钩子, 当 apache 启动加载模块时调用
void php_ap2_register_hook(apr_pool_t *p)
{
ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
// apache2 的注册钩子
ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE);
// apache2 处理请求时的钩子
ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
}定义位置在 sapi/apache2handler/sapi_apache2.c
ap_hook_pre_config, ap_hook_post_config, ap_hook_child_init 这三个钩子均在 apache 启动时调用, ap_hook_handler 钩子在每次处理请求时均会调用, 在 ap_hook_post_config 中启动 php
-
调用 php_apache_server_startup
static int php_apache_server_startup(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
// 省略
if (apache2_php_ini_path_override) {
apache2_sapi_module.php_ini_path_override = apache2_php_ini_path_override;
}
#ifdef ZTS
// 启动线程安全资源管理
tsrm_startup(1, 1, 0, NULL);
#endif
// sapi 启动
sapi_startup(&apache2_sapi_module);
//
apache2_sapi_module.startup(&apache2_sapi_module);
apr_pool_cleanup_register(pconf, NULL, php_apache_server_shutdown, apr_pool_cleanup_null);
php_apache_add_version(pconf); return OK;
}
-
-
sapi 启动
-
sapi 模块结构体
static sapi_module_struct apache2_sapi_module = {
"apache2handler",
"Apache 2.0 Handler", php_apache2_startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */ NULL, /* activate */
NULL, /* deactivate */ php_apache_sapi_ub_write, /* unbuffered write */
php_apache_sapi_flush, /* flush */
php_apache_sapi_get_stat, /* get uid */
php_apache_sapi_getenv, /* getenv */ php_error, /* error handler */ php_apache_sapi_header_handler, /* header handler */
php_apache_sapi_send_headers, /* send headers handler */
NULL, /* send header handler */ php_apache_sapi_read_post, /* read POST data */
php_apache_sapi_read_cookies, /* read Cookies */ php_apache_sapi_register_variables,
php_apache_sapi_log_message, /* Log message */
php_apache_sapi_get_request_time, /* Request Time */
NULL, /* Child Terminate */ STANDARD_SAPI_MODULE_PROPERTIES
}; 调用 tsrm_startup (启动线程安全资源管理, 此处不做进一步展开, 在线程安全时单独展开)
-
调用 sapi_startup (main/SAPI.c)
SAPI_API void sapi_startup(sapi_module_struct *sf)
{
#ifdef ZEND_SIGNALS
zend_signal_startup();
#endif sf->ini_entries = NULL;
sapi_module = *sf; #ifdef ZTS
ts_allocate_id(&sapi_globals_id, sizeof(sapi_globals_struct), (ts_allocate_ctor) sapi_globals_ctor, (ts_allocate_dtor) sapi_globals_dtor);
# ifdef PHP_WIN32
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
# endif
#else
sapi_globals_ctor(&sapi_globals);
#endif virtual_cwd_startup(); /* Could use shutdown to free the main cwd but it would just slow it down for CGI */ #ifdef PHP_WIN32
tsrm_win32_startup();
#endif reentrancy_startup();
}zend_signal_startup: 信号系统
-
sapi_globals_ctor: 创建 sapi 全局结构
static void sapi_globals_ctor(sapi_globals_struct *sapi_globals TSRMLS_DC)
{
memset(sapi_globals, 0, sizeof(*sapi_globals));
zend_hash_init_ex(&sapi_globals->known_post_content_types, 5, NULL, NULL, 1, 0);
php_setup_sapi_content_types(TSRMLS_C);
}typedef struct _sapi_globals_struct {
// server 环境上下文
void *server_context;
// 请求信息
sapi_request_info request_info;
// 头信息
sapi_headers_struct sapi_headers;
// post 字节数
int read_post_bytes;
// 是否发送头
unsigned char headers_sent;
struct stat global_stat;
// 默认 mime 类型
char *default_mimetype;
// 默认字符集
char *default_charset;
// 上传文件
HashTable *rfc1867_uploaded_files;
// post 请求最大大小
long post_max_size;
int options;
// sapi 是否已启动
zend_bool sapi_started;
// 请求时间
double global_request_time;
// 已知的 post 的 content_type
HashTable known_post_content_types;
zval *callback_func;
zend_fcall_info_cache fci_cache;
zend_bool callback_run;
} sapi_globals_struct;-
调用 php_setup_sapi_contents_types, 设置 post 请求相关的参数和处理方法
int php_setup_sapi_content_types(TSRMLS_D)
{
sapi_register_post_entries(php_post_entries TSRMLS_CC); return SUCCESS;
}static sapi_post_entry php_post_entries[] = {
{ DEFAULT_POST_CONTENT_TYPE, sizeof(DEFAULT_POST_CONTENT_TYPE)-1, sapi_read_standard_form_data, php_std_post_handler },
{ MULTIPART_CONTENT_TYPE, sizeof(MULTIPART_CONTENT_TYPE)-1, NULL, rfc1867_post_handler },
{ NULL, 0, NULL, NULL }
};struct _sapi_post_entry {
char *content_type;
uint content_type_len;
void (*post_reader)(TSRMLS_D);
void (*post_handler)(char *content_type_dup, void *arg TSRMLS_DC);
};-
调用 sapi_register_post_entries
SAPI_API int sapi_register_post_entries(sapi_post_entry *post_entries TSRMLS_DC)
{
sapi_post_entry *p=post_entries; while (p->content_type) {
if (sapi_register_post_entry(p TSRMLS_CC) == FAILURE) {
return FAILURE;
}
p++;
}
return SUCCESS;
}SAPI_API int sapi_register_post_entry(sapi_post_entry *post_entry TSRMLS_DC)
{
// 若 sapi 已启动并且正在执行当中, 不允许注册 post 结构体
if (SG(sapi_started) && EG(in_execution)) {
return FAILURE;
}
return zend_hash_add(&SG(known_post_content_types),
post_entry->content_type, post_entry->content_type_len+1,
(void *) post_entry, sizeof(sapi_post_entry), NULL);
}
-
-
调用 apache2_sapi_module.startup
static int php_apache2_startup(sapi_module_struct *sapi_module)
{
if (php_module_startup(sapi_module, &php_apache_module, 1)==FAILURE) {
return FAILURE;
}
return SUCCESS;
}-
php_apache_module 参数是 zend 引擎封装的模块结构
zend_module_entry php_apache_module = {
STANDARD_MODULE_HEADER,
"apache2handler",
apache_functions,
PHP_MINIT(apache), // 扩展为 zm_startup_##module
PHP_MSHUTDOWN(apache), // 扩展为 zm_shutdown_##module
NULL,
NULL,
PHP_MINFO(apache), // 扩展为 zm_info_##module
NULL,
STANDARD_MODULE_PROPERTIES
};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);
// 模块关闭时调用的函数
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
// 请求开始时调用的函数
int (*request_startup_func)(INIT_FUNC_ARGS);
// 请求结束时调用的函数
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
// 模块信息函数
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
// 版本
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 TSRMLS_DC);
void (*globals_dtor)(void *global TSRMLS_DC);
int (*post_deactivate_func)(void);
int module_started;
unsigned char type;
void *handle;
int module_number;
const char *build_id;
};
-
调用 php_module_startup (main/main.c)
int php_module_startup(sapi_module_struct *sf, zend_module_entry *additional_modules, uint num_additional_modules)
{
zend_utility_functions zuf;
zend_utility_values zuv;
int retval = SUCCESS, module_number=0; /* for REGISTER_INI_ENTRIES() */
char *php_os;
zend_module_entry *module; // 省略 ... module_shutdown = 0;
module_startup = 1;
// 初始化空请求
sapi_initialize_empty_request(TSRMLS_C);
// sapi 激活
sapi_activate(TSRMLS_C); if (module_initialized) {
return SUCCESS;
} sapi_module = *sf;
// php 输出功能启动
php_output_startup();
// 设置 zend 引擎使用的函数
zuf.error_function = php_error_cb;
zuf.printf_function = php_printf;
zuf.write_function = php_output_wrapper;
zuf.fopen_function = php_fopen_wrapper_for_zend;
zuf.message_handler = php_message_handler_for_zend;
zuf.block_interruptions = sapi_module.block_interruptions;
zuf.unblock_interruptions = sapi_module.unblock_interruptions;
zuf.get_configuration_directive = php_get_configuration_directive_for_zend;
zuf.ticks_function = php_run_ticks;
zuf.on_timeout = php_on_timeout;
zuf.stream_open_function = php_stream_open_for_zend;
zuf.vspprintf_function = vspprintf;
zuf.getenv_function = sapi_getenv;
zuf.resolve_path_function = php_resolve_path_for_zend;
// zend 引擎启动
zend_startup(&zuf, NULL TSRMLS_CC); // 省略 ... // 创建垃圾回收机制全局结构体
gc_globals_ctor(TSRMLS_C); // 省略 ... // 设置全局结构体的一些值
EG(bailout) = NULL;
EG(error_reporting) = E_ALL & ~E_NOTICE;
EG(active_symbol_table) = NULL;
PG(header_is_being_sent) = 0;
SG(request_info).headers_only = 0;
SG(request_info).argv0 = NULL;
SG(request_info).argc=0;
SG(request_info).argv=(char **)NULL;
PG(connection_status) = PHP_CONNECTION_NORMAL;
PG(during_request_startup) = 0;
PG(last_error_message) = NULL;
PG(last_error_file) = NULL;
PG(last_error_lineno) = 0;
EG(error_handling) = EH_NORMAL;
EG(exception_class) = NULL;
PG(disable_functions) = NULL;
PG(disable_classes) = NULL;
EG(exception) = NULL;
EG(objects_store).object_buckets = NULL; // 省略 ... // 注册一些常量
REGISTER_MAIN_STRINGL_CONSTANT("PHP_VERSION", PHP_VERSION, sizeof(PHP_VERSION)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_MAJOR_VERSION", PHP_MAJOR_VERSION, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_MINOR_VERSION", PHP_MINOR_VERSION, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_RELEASE_VERSION", PHP_RELEASE_VERSION, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_EXTRA_VERSION", PHP_EXTRA_VERSION, sizeof(PHP_EXTRA_VERSION) - 1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_VERSION_ID", PHP_VERSION_ID, CONST_PERSISTENT | CONST_CS);
#ifdef ZTS
REGISTER_MAIN_LONG_CONSTANT("PHP_ZTS", 1, CONST_PERSISTENT | CONST_CS);
#else
REGISTER_MAIN_LONG_CONSTANT("PHP_ZTS", 0, CONST_PERSISTENT | CONST_CS);
#endif
REGISTER_MAIN_LONG_CONSTANT("PHP_DEBUG", PHP_DEBUG, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_OS", php_os, strlen(php_os), CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_SAPI", sapi_module.name, strlen(sapi_module.name), CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("DEFAULT_INCLUDE_PATH", PHP_INCLUDE_PATH, sizeof(PHP_INCLUDE_PATH)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PEAR_INSTALL_DIR", PEAR_INSTALLDIR, sizeof(PEAR_INSTALLDIR)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PEAR_EXTENSION_DIR", PHP_EXTENSION_DIR, sizeof(PHP_EXTENSION_DIR)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_EXTENSION_DIR", PHP_EXTENSION_DIR, sizeof(PHP_EXTENSION_DIR)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_PREFIX", PHP_PREFIX, sizeof(PHP_PREFIX)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_BINDIR", PHP_BINDIR, sizeof(PHP_BINDIR)-1, CONST_PERSISTENT | CONST_CS);
#ifndef PHP_WIN32
REGISTER_MAIN_STRINGL_CONSTANT("PHP_MANDIR", PHP_MANDIR, sizeof(PHP_MANDIR)-1, CONST_PERSISTENT | CONST_CS);
#endif
REGISTER_MAIN_STRINGL_CONSTANT("PHP_LIBDIR", PHP_LIBDIR, sizeof(PHP_LIBDIR)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_DATADIR", PHP_DATADIR, sizeof(PHP_DATADIR)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_SYSCONFDIR", PHP_SYSCONFDIR, sizeof(PHP_SYSCONFDIR)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_LOCALSTATEDIR", PHP_LOCALSTATEDIR, sizeof(PHP_LOCALSTATEDIR)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_CONFIG_FILE_PATH", PHP_CONFIG_FILE_PATH, strlen(PHP_CONFIG_FILE_PATH), CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_CONFIG_FILE_SCAN_DIR", PHP_CONFIG_FILE_SCAN_DIR, sizeof(PHP_CONFIG_FILE_SCAN_DIR)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_SHLIB_SUFFIX", PHP_SHLIB_SUFFIX, sizeof(PHP_SHLIB_SUFFIX)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_STRINGL_CONSTANT("PHP_EOL", PHP_EOL, sizeof(PHP_EOL)-1, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_MAXPATHLEN", MAXPATHLEN, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_INT_MAX", LONG_MAX, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_INT_SIZE", sizeof(long), CONST_PERSISTENT | CONST_CS); #ifdef PHP_WIN32
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_VERSION_MAJOR", EG(windows_version_info).dwMajorVersion, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_VERSION_MINOR", EG(windows_version_info).dwMinorVersion, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_VERSION_BUILD", EG(windows_version_info).dwBuildNumber, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_VERSION_PLATFORM", EG(windows_version_info).dwPlatformId, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_VERSION_SP_MAJOR", EG(windows_version_info).wServicePackMajor, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_VERSION_SP_MINOR", EG(windows_version_info).wServicePackMinor, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_VERSION_SUITEMASK", EG(windows_version_info).wSuiteMask, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_VERSION_PRODUCTTYPE", EG(windows_version_info).wProductType, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_NT_DOMAIN_CONTROLLER", VER_NT_DOMAIN_CONTROLLER, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_NT_SERVER", VER_NT_SERVER, CONST_PERSISTENT | CONST_CS);
REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_NT_WORKSTATION", VER_NT_WORKSTATION, CONST_PERSISTENT | CONST_CS);
#endif
// php 命令初始化, 主要是设置 php 命令路径
php_binary_init(TSRMLS_C);
if (PG(php_binary)) {
REGISTER_MAIN_STRINGL_CONSTANT("PHP_BINARY", PG(php_binary), strlen(PG(php_binary)), CONST_PERSISTENT | CONST_CS);
} else {
REGISTER_MAIN_STRINGL_CONSTANT("PHP_BINARY", "", 0, CONST_PERSISTENT | CONST_CS);
}
// 注册 php 输出功能相关的一些常量
php_output_register_constants(TSRMLS_C);
// 注册 php 上传功能相关的一些常量
php_rfc1867_register_constants(TSRMLS_C); // 读取 php.ini 配置
if (php_init_config(TSRMLS_C) == FAILURE) {
return FAILURE;
} // 将 php.ini 文件读取的配置注册成 ini_entry
REGISTER_INI_ENTRIES(); // 注册 zend 引擎的 ini_entry
zend_register_standard_ini_entries(TSRMLS_C); /* Disable realpath cache if an open_basedir is set */
if (PG(open_basedir) && *PG(open_basedir)) {
CWDG(realpath_cache_size_limit) = 0;
} // php 流的初始化
if (php_init_stream_wrappers(module_number TSRMLS_CC) == FAILURE) {
php_printf("PHP: Unable to initialize stream url wrappers.\n");
return FAILURE;
} zuv.html_errors = 1;
zuv.import_use_extension = ".php";
// 注册超全局数组 $_GET, $_POST, $_COOKIE, ...
php_startup_auto_globals(TSRMLS_C);
zend_set_utility_values(&zuv);
// 启动 sapi 的处理请求功能, 设置一些处理函数
php_startup_sapi_content_types(TSRMLS_C); // 注册 php 内置扩展 (module_registry)
if (php_register_internal_extensions_func(TSRMLS_C) == FAILURE) {
php_printf("Unable to start builtin modules\n");
return FAILURE;
}
// 注册附加扩展 (module_registry)
php_register_extensions_bc(additional_modules, num_additional_modules TSRMLS_CC);
// 注册 php.ini 文件设置的扩展 (zend_extensions)
php_ini_register_extensions(TSRMLS_C); // 启动各模块 (module_registry), 调用各模块的 startup 方法
zend_startup_modules(TSRMLS_C);
// 启动各扩展 (zend_extentions), 调用各扩展的 startup 方法
zend_startup_extensions();
// 统计各模块的启动情况
zend_collect_module_handlers(TSRMLS_C); /* register additional functions */
if (sapi_module.additional_functions) {
if (zend_hash_find(&module_registry, "standard", sizeof("standard"), (void**)&module)==SUCCESS) {
EG(current_module) = module;
zend_register_functions(NULL, sapi_module.additional_functions, NULL, MODULE_PERSISTENT TSRMLS_CC);
EG(current_module) = NULL;
}
} // 禁用函数
php_disable_functions(TSRMLS_C);
// 禁用类
php_disable_classes(TSRMLS_C); // 省略 ... // sapi
sapi_deactivate(TSRMLS_C);
module_startup = 0;
// 关闭内存管理
shutdown_memory_manager(1, 0 TSRMLS_CC);
zend_interned_strings_snapshot(TSRMLS_C); /* we're done */
return retval;
}初始化 zend 引擎使用的一些函数和值
初始化 sapi_global 的一些值 (通过初始化空请求, sapi 激活)
初始化全局结构体的一些值
zend 引擎启动
设置一些常量
加载 php.ini 配置
注册各子模块和扩展
启动各子模块和扩展
禁用函数和类
-
调用 apr_pool_cleanup_register
-
sapi 生命周期
-
MINIT
若不细致区分 sapi 启动前的准备工作与启动过程, 上面的部分均可以看成是 sapi MINIT 过程
若细致区分, sapi 启动前的准备工作是在注册 php 内置扩展之前的部分; 之后的部分为 MINIT 过程 RINIT
RSHUTDOWN
MSHUTDOWN
对于以上内容的更细致的点, 比如线程资源安全, zend 引擎启动, 全局结构体, php.ini 如何加载配置等等等等, 由于篇幅问题, 不在此赘述, 内容太多消化不了, 会在后续一个一个详细剖析, 本节仅涉及到 sapi 生命周期当中的 MINIT 过程