PHP SAPI CLI启动流程结合mysql扩展源码学习
在之前的学习中,大概抽象的了解了一个CLI进程的生命周期
接下来结合这张图, 我们再结合mysql扩展来看看扩展是如何实现回调的。
1、call each extension MINIT函数,(https://github.com/php/php-src/blob/PHP-5.4.41/ext/mysql/php_mysql.c#L1184)
PHP的扩展模块是在php.ini中定义的,php进程在初始化的时候会循环调用所有扩展模块的MINIT函数,以mysql扩展举例,
以下是mysql扩展的结构体定义, php在启动时会回调这些函数,:
/* {{{ mysql_module_entry
*/
zend_module_entry mysql_module_entry = {
#if ZEND_MODULE_API_NO >= 20050922
STANDARD_MODULE_HEADER_EX, NULL,
mysql_deps,
#elif ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"mysql",
mysql_functions, //注册了一些该扩展的方法, 比如mysql_connect等,函数在扩展类中已经实现
ZEND_MODULE_STARTUP_N(mysql), //模块初始化回调方法, MINIT ,见下方
PHP_MSHUTDOWN(mysql),
PHP_RINIT(mysql), //载入php文件解析后执行的方法,RINIT, 见下方实现
PHP_RSHUTDOWN(mysql), //
PHP_MINFO(mysql), // php -m回调这个方法,主要是返回当前扩展的信息
"1.0",
PHP_MODULE_GLOBALS(mysql),
PHP_GINIT(mysql),
NULL,
NULL,
STANDARD_MODULE_PROPERTIES_EX
};
/* }}} */
一、下面来看看php_mysql扩展的MINIT实现函数:
/* {{{ PHP_MINIT_FUNCTION
*/
ZEND_MODULE_STARTUP_D(mysql)
{
//1.可以调用REGISTER_INI_ENTRIES()来完成。REGISTER_INI_ENTRIES会根据当前模块所需要的配置项名称,
//去configuration_hash查找用户设置的配置值,并更新到模块自己的全局空间中。具体看下文
REGISTER_INI_ENTRIES();
//这里是一些资源类型的定义, 比如长连接的类型, mysql link persistent,用var_dump($mysql)查看的时候会显示 Resource(mysql link persistent)
le_result = zend_register_list_destructors_ex(_free_mysql_result, NULL, "mysql result", module_number);
le_link = zend_register_list_destructors_ex(_close_mysql_link, NULL, "mysql link", module_number);
le_plink = zend_register_list_destructors_ex(NULL, _close_mysql_plink, "mysql link persistent", module_number);
Z_TYPE(mysql_module_entry) = type;
//注册一些静态变量
REGISTER_LONG_CONSTANT("MYSQL_ASSOC", MYSQL_ASSOC, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("MYSQL_NUM", MYSQL_NUM, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("MYSQL_BOTH", MYSQL_BOTH, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("MYSQL_CLIENT_COMPRESS", CLIENT_COMPRESS, CONST_CS | CONST_PERSISTENT);
#if MYSQL_VERSION_ID >= 40000
REGISTER_LONG_CONSTANT("MYSQL_CLIENT_SSL", CLIENT_SSL, CONST_CS | CONST_PERSISTENT);
#endif
REGISTER_LONG_CONSTANT("MYSQL_CLIENT_INTERACTIVE", CLIENT_INTERACTIVE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("MYSQL_CLIENT_IGNORE_SPACE", CLIENT_IGNORE_SPACE, CONST_CS | CONST_PERSISTENT);
#ifndef MYSQL_USE_MYSQLND
#if MYSQL_VERSION_ID >= 40000
if (mysql_server_init(0, NULL, NULL)) {
return FAILURE;
}
#endif
#endif
#ifdef MYSQL_USE_MYSQLND
mysqlnd_reverse_api_register_api(&mysql_reverse_api TSRMLS_CC);
#endif
return SUCCESS;
}
/* }}} */
下面来看看mysql扩展定义了需要INI里面的哪些配置项:
/* {{{ PHP_INI */
PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN("mysql.allow_persistent", "1", PHP_INI_SYSTEM, OnUpdateLong, allow_persistent, zend_mysql_globals, mysql_globals)
STD_PHP_INI_ENTRY_EX("mysql.max_persistent", "-1", PHP_INI_SYSTEM, OnUpdateLong, max_persistent, zend_mysql_globals, mysql_globals, display_link_numbers)
STD_PHP_INI_ENTRY_EX("mysql.max_links", "-1", PHP_INI_SYSTEM, OnUpdateLong, max_links, zend_mysql_globals, mysql_globals, display_link_numbers)
STD_PHP_INI_ENTRY("mysql.default_host", NULL, PHP_INI_ALL, OnUpdateString, default_host, zend_mysql_globals, mysql_globals)
STD_PHP_INI_ENTRY("mysql.default_user", NULL, PHP_INI_ALL, OnUpdateString, default_user, zend_mysql_globals, mysql_globals)
STD_PHP_INI_ENTRY("mysql.default_password", NULL, PHP_INI_ALL, OnUpdateString, default_password, zend_mysql_globals, mysql_globals)
PHP_INI_ENTRY("mysql.default_port", NULL, PHP_INI_ALL, OnMySQLPort)
#ifdef MYSQL_UNIX_ADDR
STD_PHP_INI_ENTRY("mysql.default_socket", MYSQL_UNIX_ADDR,PHP_INI_ALL,OnUpdateStringUnempty, default_socket, zend_mysql_globals, mysql_globals)
#else
STD_PHP_INI_ENTRY("mysql.default_socket", NULL, PHP_INI_ALL, OnUpdateStringUnempty, default_socket, zend_mysql_globals, mysql_globals)
#endif
STD_PHP_INI_ENTRY("mysql.connect_timeout", "60", PHP_INI_ALL, OnUpdateLong, connect_timeout, zend_mysql_globals, mysql_globals)
STD_PHP_INI_BOOLEAN("mysql.trace_mode", "0", PHP_INI_ALL, OnUpdateLong, trace_mode, zend_mysql_globals, mysql_globals)
STD_PHP_INI_BOOLEAN("mysql.allow_local_infile", "1", PHP_INI_SYSTEM, OnUpdateLong, allow_local_infile, zend_mysql_globals, mysql_globals)
PHP_INI_END()
/* }}} */
二、接下来看看mysql扩展对于RINIT的实现
/* {{{ PHP_RINIT_FUNCTION
*/
PHP_RINIT_FUNCTION(mysql)
{
#if !defined(MYSQL_USE_MYSQLND) && defined(ZTS) && MYSQL_VERSION_ID >= 40000
if (mysql_thread_init()) { //初始化连接mysql的线程
return FAILURE;
}
#endif
MySG(default_link)=-1; //设置当前类的一些全局变量
MySG(num_links) = MySG(num_persistent);
/* Reset connect error/errno on every request */
MySG(connect_error) = NULL; //每次执行这个函数都会清空之前保存的错误信息
MySG(connect_errno) =0;
MySG(result_allocated) = 0;
return SUCCESS;
}
/* }}} */
当然了,扩展中必须对一些参数进行实现,比如php -m就会调用
/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(mysql)
{
char buf[32];
php_info_print_table_start();
php_info_print_table_header(2, "MySQL Support", "enabled");
snprintf(buf, sizeof(buf), "%ld", MySG(num_persistent));
php_info_print_table_row(2, "Active Persistent Links", buf);
snprintf(buf, sizeof(buf), "%ld", MySG(num_links));
php_info_print_table_row(2, "Active Links", buf);
php_info_print_table_row(2, "Client API version", mysql_get_client_info());
#if !defined (PHP_WIN32) && !defined (NETWARE) && !defined(MYSQL_USE_MYSQLND)
php_info_print_table_row(2, "MYSQL_MODULE_TYPE", PHP_MYSQL_TYPE);
php_info_print_table_row(2, "MYSQL_SOCKET", MYSQL_UNIX_ADDR);
php_info_print_table_row(2, "MYSQL_INCLUDE", PHP_MYSQL_INCLUDE);
php_info_print_table_row(2, "MYSQL_LIBS", PHP_MYSQL_LIBS);
#endif
php_info_print_table_end();
DISPLAY_INI_ENTRIES();
}
/* }}} */
三、 执行已经解释完成的php scripts脚本文件
execute
四、R SHUTDOWN , 结束RINIT所做的操作,下面来看看mysql的这个函数实现
/* {{{ PHP_RSHUTDOWN_FUNCTION
*/
PHP_RSHUTDOWN_FUNCTION(mysql)
{
#if !defined(MYSQL_USE_MYSQLND) && defined(ZTS) && MYSQL_VERSION_ID >= 40000
mysql_thread_end(); //结束连接线程,RINIT函数是mysql_thread_init()
#endif
if (MySG(trace_mode)) { //如果释放失败,这里是打印tracee错误信息了
if (MySG(result_allocated)){
php_error_docref("function.mysql-free-result" TSRMLS_CC, E_WARNING, "%lu result set(s) not freed. Use mysql_free_result to free result sets which were requested using mysql_query()", MySG(result_allocated));
}
}
if (MySG(connect_error)!=NULL) {
efree(MySG(connect_error));
}
#if defined(A0) && defined(MYSQL_USE_MYSQLND)
zend_hash_apply(&EG(persistent_list), (apply_func_t) php_mysql_persistent_helper TSRMLS_CC);
#endif
return SUCCESS;
}
/* }}} */
五、到这里php脚本就执行完成了,由php内核调用的。暂时不管php内核如何执行的脚本。
六、脚本执行结束后,内核会调用所有模块的MSHUTDOWN函数,下面来看看mysql的改回调函数实现。
/* {{{ PHP_MSHUTDOWN_FUNCTION
*/
PHP_MSHUTDOWN_FUNCTION(mysql)
{
#ifndef MYSQL_USE_MYSQLND
#if MYSQL_VERSION_ID >= 40000
#ifdef PHP_WIN32 #这里是windows平台的, 不用管
unsigned long client_ver = mysql_get_client_version();
/*
Can't call mysql_server_end() multiple times prior to 5.0.46 on Windows.
PHP bug#41350 MySQL bug#25621
*/
if ((client_ver >= 50046 && client_ver < 50100) || client_ver > 50122) {
mysql_server_end();
}
#else
mysql_server_end(); //linux下只有一个函数执行,基本对应了MINIT,当然有MINIT注册的一些全局变量最后由php内核进行回收
#endif
#endif
#endif
UNREGISTER_INI_ENTRIES(); //不需要获取INI配置中的信息了
return SUCCESS;
}
七、php内核回收释放内存,进程结束。
总结,明白了CLI的生命周期,结合之前的学习,接下来看看其他SAPI的生命周期就容易很多。
APACHE, FAST-CGI等SAPI的运行方式如下:
以mysql扩展举例, 一个请求结束后,mysql扩展会执行mysql_thread_end(), 结束线程,但是不会结束mysql_server.。
多进程模式下,不同的SAPI接收请求参数的方式不一致,比如FAST-CGI遵循了FAST-CGI协议来接收socket参数。