您的位置:首页 > 编程语言 > PHP开发

PHP SAPI CLI启动流程结合mysql扩展源码学习

2017-07-07 12:33 561 查看
在之前的学习中,大概抽象的了解了一个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参数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  CLI CGI FASTCGI