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

[李景山php] 20170505深入理解PHP内核[读书笔记]--第二章:用户代码执行-1

2017-05-06 12:24 716 查看
不识庐山真面膜,只缘身在此山中。

PHP代码运行示意图:

第一步:书写一段php代码。

<?php
echo "Hi";
?>


第二步:传入PHP内核

2.1 首先通过词法分析

2.2 然后通过语法分析

2.3 通过Zend Engine 执行

第三步:输出Hi字符串

本章关注于:

1 PHP内部的生命周期

2 SAPI接口

3 词法分析与语法分析

4 什么是Opcodes

第一节: 生命周期和Zend引擎

一切的开始:SAPI接口

SAPI(Server Application Programming Interface)指的是PHP具体应用的编程接口,就像PC一样,无论安装哪些操作系统,只要满足了PC的接口规范都可以在PC上正常运行,PHP脚本要执行有很多种方式,通过web服务器,或者直接在命令行下,也可以嵌入其它程序中。

SAPI程序开始和结束

PHP开始执行以后会经过两个主要的阶段:处理请求之前的开始阶段和请求之后的结束阶段。

请求开始阶段:

第一个过程是模块初始化阶段(MINIT),在整个SAPI生命周期内(例如Apache启动以后的整个声明周期内或者命令行程序整个执行过程中),该过程只进行一次。

第二个过程是模块激活阶段(RINIT),该过程发生在请求阶段,例如通过curl请求某个页面,则在每次请求之前都会进行模块激活(RINIT请求开始)。例如PHP注册了一些扩展模块,则在MINIT阶段会回调所有模块的MINIT函数。模块在这个阶段可以进行一些初始化工作,例如注册常量,定义模块使用的类等等。模块在实现时可以通过如下宏来实现这些回调函数:

PHP_MINIT_FUNCTION(myphpextension)
{
//注册常量或者类等初始化操作
return SUCCESS;
}


请求到达之后PHP初始化执行脚本的基本环境,例如创建一个执行环境,包括保存PHP运行过程中变量名称和值内容的符号表,以及当前所有的函数以及类等信息的符号表。然后PHP会调用所有模块的RINIT函数,在这个阶段各个模块也可以执行一些相关的操作,模块的RINIT函数和MINIT回调函数类似:

PHP_RINIT_FUNCTION(myphpextension)
{
//例如记录请求开始时间
//随后在请求结束的时候
return SUCCESS;
}


请求处理完后进入了结束阶段,一般脚本执行末尾或者通过调用exit()或die()函数,PHP都将进入结束阶段。和开始阶段对应,结束阶段也分为两个环节,一个在请求结束后停用模块(RSHUTDOWN,对应RINIT),一个在SAPI生命周期结束(Web服务器退出或者命令行脚本执行完毕退出)时关闭模块(MSHUTDOWN,对应MINIT).


PHP_RSHUTDOWN_FUNCTION(myphpextension)
{
//例如记录请求结束时间,并把相应的信息写入到日志文件中。
return SUCCESS;
}


小小的总结一下:

启动php的执行顺序:

1 Mode Init —–》启动php 总 模块 —–》 对应函数 PHP_MINIT_FUNCTION(myphpextension)

2 Run Init —–》启动php 运行 模块 —–》 对应函数 PHP_RINIT_FUNCTION(myphpextension)

3 执行用户定义的任务

4 Run shutdown —–》善后php 运行 模块 —–》 对应函数 PHP_RSHUTDOWN_FUNCTION(myphpextension)

5 Mode shutdown —–》善后php 总 模块 —–》 对应函数 PHP_MSHUTDOWN_FUNCTION(myphpextension)

单进程SAPI生命周期

CLI/CGI模式的PHP属于单进程的SAPI模式。这类的请求在处理一次请求后就关闭。

单个php文件运行的流程:


步骤:

1 $php -f test.php———————————————>通过php的CLI方式执行test.php文件

2 Call each extension’s MINIT———————————>初始化/调用 php模块的 MINIT 方法

3 Request test.php——————————————–>包含 test.php 执行文件

4 Call each extension’s RINIT———————————->初始化/调用 php模块的 RINIT 方法

5 Execute test.php——————————————–>真正执行 test.php文件

6 Call each extension’s RSHUTDOWN————————->调用 php模块的 RSHUTDOWN 运行善后模块

7 Finish cleaning up after test.php—————————>完成执行的善后行动

8 Call each extension’s MSHUTDOWN————————->调用每个php模块的 MSHUTDOWN 的模块

9 Terminate php————————————————->结束PHP

合理请求流程包含:3,4,5,6,7

全部php的生命周期:1~9

分析如下:

当通过php 执行 test.php文件的时候,

第一步:调用全部的初始化部分

第二步:加载需要执行的文件

第三步:调用全部的运行初始化部分

第四步:执行运行文件

第五步:调用全部的运行结束模块

第六步:完善运行test.php的清理工作

第七步:调用全部模块的结束工作

第八步:停掉php进程。

上述只是总体的运行流程 ,下面可以细化一下补充情况。

启动

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

初始化若干全局变量

这里的初始化全局变量大多数情况下是将其设置为NULL,有一些除外,比如设置zuf(zend_utitity_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源文件进行词法分析,语法分析,中间代码执行的函数指针的赋值,初始化若干Hash Table(比如函数表,常量表等等),为ini文件解析做准备,为PHP源文件解析做准备,注册内置函数(如 stren,define等),注册标准常量(如E_ALL,TRUE,NULL等),注册GLOBALS全局变量等。


解析php.ini

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

INI_DEFAULT("report_zend_debug","0");
INI_DEFAULT("display_errors","1");

不过在其它模式下却没有这样的初始化操作。接下来会的各种操作都是查找ini文件:
1 判读是否有php_ini_path_override,在cli模式下可以通过-c参数指定此路径(在php的命令参数中-c表示在指定的路径中查找ini文件)。
2 如果没有php_ini_path_override,判读php_ini_ignore是否为空(忽略php.ini配置,这里也就CLI模式下有用,使用-n参数)。
3 如果不忽略ini配置,则开始处理php_ini_search_path(查找ini文件的路径),这些路径包括CWD(当前路径,不过这种不适用CLI模式),执行脚本所在目录,环境变量PATH和PHPRC和配置文件中的PHP_CONFIG_FILE_PATH的值。
4 在准备完查找路径后,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函数用来注册静态构建的模块,也就是默认加载的模块,我们可以将其认为内置模块。在PHP5.3.0版本中内置的模块包括PHP标准扩展模块(/ext/standart/目录,这里是我们用的最频繁的函数,比如字符串函数,数学函数,数组操作函数等等),日历扩展模块,FTP扩展模块,session扩展模块等。这些内置模块并不是一成不变的,在不同的PHP模版中,由于同时间的需求或其它影响因素会导致这些默认加载的模块会变化,比如从代码中我们就可以看到mysql,xml等扩展模块曾经或将来作为内置模块出现。
模块初始化执行两个操作:1 将这些模块注册到已注册模块列表(module_register),如果注册的模块已经注册过了,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_classess函数用来禁用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_extension也是在这里被遍历调用activate函数。


激活SAPI

环境初始化

模块请求初始化

运行

deactivation

结束

flush

关闭Zend引擎

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  php 读书笔记 内核 zend
相关文章推荐