热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

细读PHP的生命周期

在调用每个模块的模块初始化前,会有一个初始化的过程,它包括:

启动

在调用每个模块的模块初始化前,会有一个初始化的过程,它包括:


初始化若干全局变量

这里的初始化全局变量大多数情况下是将其设置为NULL,有一些除外,比如设置zuf(zend_utility_functions),以zuf.printf_function = php_printf为例,这里的php_printf在zend_startup函数中会被赋值给zend_printf作为全局函数指针使用,而zend_printf函数通常会作为常规字符串输出使用,比如显示程序调用栈的debug_print_backtrace就是使用它打印相关信息。


初始化若干常量

这里的常量是PHP自己的一些常量,这些常量要么是硬编码在程序中,比如PHP_VERSION,要么是写在配置头文件中,比如PEAR_EXTENSION_DIR,这些是写在config.w32.h文件中。


初始化ZEND引擎和核心组件

前面提到的zend_startup函数的作用就是初始化ZEND引擎,这里的初始化操作包括内存管理初始化、全局使用的函数指针初始化(如前面所说的zend_printf等),对PHP源文件进行词法分析、语法分析、中间代码执行的函数指针的赋值,初始化若干HashTable(比如函数表,常量表等等),为ini文件解析做准备,为PHP源文件解析做准备,注册内置函数(如strlen、define等),注册标准常量(如E_ALL、TRUE、NULL等)、注册GLOBALS全局变量等。


解析php.ini

php_init_config函数的作用是读取php.ini文件,设置配置参数,加载zend扩展并注册PHP扩展函数。此函数分为如下几步:初始化参数配置表,调用当前模式下的ini初始化配置,比如CLI模式下,会做如下初始化:

INI_DEFAULT("report_zend_debug", "0");

INI_DEFAULT("display_errors", "1");

不过在其它模式下却没有这样的初始化操作。接下来会的各种操作都是查找ini文件:

判断是否有php_ini_path_override,在CLI模式下可以通过-c参数指定此路径(在php的命令参数中-c表示在指定的路径中查找ini文件)。

如果没有php_ini_path_override,判断php_ini_ignore是否为非空(忽略php.ini配置,这里也就CLI模式下有用,使用-n参数)。

如果不忽略ini配置,则开始处理php_ini_search_path(查找ini文件的路径),这些路径包括CWD(当前路径,不过这种不适用CLI模式)、执行脚本所在目录、环境变量PATH和PHPRC和配置文件中的PHP_CONFIG_FILE_PATH的值。

在准备完查找路径后,PHP会判断现在的ini路径(php_ini_file_name)是否为文件和是否可打开。如果这里ini路径是文件并且可打开,则会使用此文件, 也就是CLI模式下通过-c参数指定的ini文件的优先级是最高的,其次是PHPRC指定的文件,第三是在搜索路径中查找php-%sapi-module-name%.ini文件(如CLI模式下应该是查找php-cli.ini文件),最后才是搜索路径中查找php.ini文件。


全局操作函数的初始化

php_startup_auto_globals函数会初始化在用户空间所使用频率很高的一些全局变量,如:$_GET、$_POST、$_FILES等。这里只是初始化,所调用的zend_register_auto_global函数也只是将这些变量名添加到CG(auto_globals)这个变量表。

php_startup_sapi_content_types函数用来初始化SAPI对于不同类型内容的处理函数,这里的处理函数包括POST数据默认处理函数、默认数据处理函数等。

初始化静态构建的模块和共享模块(MINIT)

php_register_internal_extensions_func函数用来注册静态构建的模块,也就是默认加载的模块,我们可以将其认为为内置模块。在PHP5.3.0版本中内置的模块包括PHP标准扩展模块(/ext/standard/目录,这里是我们用的最频繁的函数,比如字符串函数,数学函数,数组操作函数等等),日历扩展模块、FTP扩展模块、 session扩展模块等。这些内置模块并不是一成不变的,在不同的PHP模板中,由于不同时间的需求或其它影响因素会导致这些默认加载的模块会变化,比如从代码中我们就可以看到mysql、xml等扩展模块曾经或将来会作为内置模块出现。


模块初始化会执行两个操作:

1. 将这些模块注册到已注册模块列表(module_registry),如果注册的模块已经注册过了,PHP会报Module XXX already loaded的错误。

2. 将每个模块中包含的函数注册到函数表( CG(function_table) ),如果函数无法添加,则会报 Unable to register functions, unable to load。

在注册了静态构建的模块后,PHP会注册附加的模块,不同的模式下可以加载不同的模块集,比如在CLI模式下是没有这些附加的模块的。

在内置模块和附加模块后,接下来是注册通过共享对象(比如DLL)和php.ini文件灵活配置的扩展。

在所有的模块都注册后,PHP会马上执行模块初始化操作(zend_startup_modules)。它的整个过程就是依次遍历每个模块,调用每个模块的模块初始化函数,也就是在本小节前面所说的用宏PHP_MINIT_FUNCTION包含的内容。


禁用函数和类

php_disable_functions函数用来禁用PHP的一些函数。这些被禁用的函数来自PHP的配置文件的disable_functions变量。其禁用的过程是调用zend_disable_function函数将指定的函数名从CG(function_table)函数表中删除。

php_disable_classes函数用来禁用PHP的一些类。这些被禁用的类来自PHP的配置文件的disable_classes变量。其禁用的过程是调用zend_disable_class函数将指定的类名从CG(class_table)类表中删除。


ACTIVATION

在处理了文件相关的内容,PHP会调用php_request_startup做请求初始化操作。请求初始化操作,除了图中显示的调用每个模块的请求初始化函数外,还做了较多的其它工作,其主要内容如下:


激活ZEND引擎

gc_reset函数用来重置垃圾收集机制,当然这是在PHP5.3之后才有的。

init_compiler函数用来初始化编译器,比如将编译过程中在放opcode的数组清空,准备编译时用来的数据结构等等。

init_executor函数用来初始化中间代码执行过程。在编译过程中,函数列表、类列表等都存放在编译时的全局变量中,在准备执行过程时,会将这些列表赋值给执行的全局变量中,如:EG(function_table) = CG(function_table); 中间代码执行是在PHP的执行虚拟栈中,初始化时这些栈等都会一起被初始化。除了栈,还有存放变量的符号表(EG(symbol_table))会被初始化为50个元素的hashtable,存放对象的EG(objects_store)被初始化了1024个元素。 PHP的执行环境除了上面的一些变量外,还有错误处理,异常处理等等,这些都是在这里被初始化的。通过php.ini配置的zend_extensions也是在这里被遍历调用activate函数。


激活SAPI

sapi_activate函数用来初始化SG(sapi_headers)和SG(request_info),并且针对HTTP请求的方法设置一些内容,比如当请求方法为HEAD时,设置SG(request_info).headers_Only=1;此函数最重要的一个操作是处理请求的数据,其最终都会调用sapi_module.default_post_reader。而sapi_module.default_post_reader在前面的模块初始化是通过php_startup_sapi_content_types函数注册了默认处理函数为main/php_content_types.c文件中php_default_post_reader函数。此函数会将POST的原始数据写入$HTTP_RAW_POST_DATA变量。

在处理了post数据后,PHP会通过sapi_module.read_COOKIEs读取COOKIE的值,在CLI模式下,此函数的实现为sapi_cli_read_COOKIEs,而在函数体中却只有一个return NULL;

如果当前模式下有设置activate函数,则运行此函数,激活SAPI,在CLI模式下此函数指针被设置为NULL。


环境初始化

这里的环境初始化是指在用户空间中需要用到的一些环境变量初始化,这里的环境包括服务器环境、请求数据环境等。实际到我们用到的变量,就是$_POST、$_GET、$_COOKIE、$_SERVER、$_ENV、$_FILES。和sapi_module.default_post_reader一样,sapi_module.treat_data的值也是在模块初始化时,通过php_startup_sapi_content_types函数注册了默认数据处理函数为main/php_variables.c文件中php_default_treat_data函数。

以$_COOKIE为例,php_default_treat_data函数会对依据分隔符,将所有的COOKIE拆分并赋值给对应的变量。


模块请求初始化

PHP通过zend_activate_modules函数实现模块的请求初始化,也就是我们在图中看到Call each extension’s RINIT。此函数通过遍历注册在module_registry变量中的所有模块,调用其RINIT方法实现模块的请求初始化操作。


运行

php_execute_script函数包含了运行PHP脚本的全部过程。

当一个PHP文件需要解析执行时,它可能会需要执行三个文件,其中包括一个前置执行文件、当前需要执行的主文件和一个后置执行文件。非当前的两个文件可以在php.ini文件通过auto_prepend_file参数和auto_append_file参数设置。如果将这两个参数设置为空,则禁用对应的执行文件。

对于需要解析执行的文件,通过zend_compile_file(compile_file函数)做词法分析、语法分析和中间代码生成操作,返回此文件的所有中间代码。如果解析的文件有生成有效的中间代码,则调用zend_execute(execute函数)执行中间代码。如果在执行过程中出现异常并且用户有定义对这些异常的处理,则调用这些异常处理函数。在所有的操作都处理完后,PHP通过EG(return_value_ptr_ptr)返回结果。


DEACTIVATION

PHP关闭请求的过程是一个若干个关闭操作的集合,这个集合存在于php_request_shutdown函数中。这个集合包括如下内容:

调用所有通过register_shutdown_function()注册的函数。这些在关闭时调用的函数是在用户空间添加进来的。一个简单的例子,我们可以在脚本出错时调用一个统一的函数,给用户一个友好一些的页面,这个有点类似于网页中的404页面。

执行所有可用的__destruct函数。这里的析构函数包括在对象池(EG(objects_store)中的所有对象的析构函数以及EG(symbol_table)中各个元素的析构方法。

将所有的输出刷出去。

发送HTTP应答头。这也是一个输出字符串的过程,只是这个字符串可能符合某些规范。

遍历每个模块的关闭请求方法,执行模块的请求关闭操作,这就是我们在图中看到的Call each extension’s RSHUTDOWN。

销毁全局变量表(PG(http_globals))的变量。

通过zend_deactivate函数,关闭词法分析器、语法分析器和中间代码执行器。

调用每个扩展的post-RSHUTDOWN函数。只是基本每个扩展的post_deactivate_func函数指针都是NULL。

关闭SAPI,通过sapi_deactivate销毁SG(sapi_headers)、SG(request_info)等的内容。

关闭流的包装器、关闭流的过滤器。

关闭内存管理。

重新设置最大执行时间


结束

最终到了要收尾的地方了。


flush

sapi_flush将最后的内容刷新出去。其调用的是sapi_module.flush,在CLI模式下等价于fflush函数。

关闭ZEND引擎

zend_shutdown将关闭ZEND引擎。


此时对应图中的流程,我们应该是执行每个模块的关闭模块操作。在这里只有一个zend_hash_graceful_reverse_destroy函数将module_registry销毁了。当然,它最终也是调用了关闭模块的方法的,其根源在于在初始化module_registry时就设置了这个hash表析构时调用ZEND_MODULE_DTOR宏。而ZEND_MODULE_DTOR宏对应的是module_destructor函数。在此函数中会调用模块的module_shutdown_func方法,即PHP_RSHUTDOWN_FUNCTION宏产生的那个函数。

在关闭所有的模块后,PHP继续销毁全局函数表,销毁全局类表、销售全局变量表等。通过zend_shutdown_extensions遍历zend_extensions所有元素,调用每个扩展的shutdown函数。


推荐阅读
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
    本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文由编程笔记小编整理,介绍了PHP中的MySQL函数库及其常用函数,包括mysql_connect、mysql_error、mysql_select_db、mysql_query、mysql_affected_row、mysql_close等。希望对读者有一定的参考价值。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
author-avatar
用户0h2eqqbg44
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有