PHP7内核剖析(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-18 11:13:27

点击下载

作者:秦朋

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

PHP7内核剖析

PHP7内核剖析试读:

前言

为什么要写这本书

PHP作为最流行的语言之一,自第一个版本发布至今的二十几年里经历了多次重大的改进,尤其是 PHP7 版本的发布,其最大的亮点在于性能上的提升,比 PHP5 快了一倍。随着 PHP7的不断普及,越来越多的项目从PHP5迁移到了PHP7,毫无疑问,PHP7将成为PHP历史上里程碑式的一个版本。我是在大学时代接触到的PHP,初次相识就被其简洁、易用的语法所吸引了。在工作后的几年里,我一直使用PHP作为主要的开发语言。当然,除了PHP,我也使用过很多其他语言,比如C、C++、Java、Golang、Python等,不同的语言有各自的特点、优势,让我印象最深的、也让我最喜欢的有C、Golang、PHP。

●C

这是我评价最高的一门语言,其强大的操控能力、简洁的语法、易于理解的处理方式无一不让我折服。编程语言本身只是控制计算机的一种工具,然而很多高级语言过度隔离了人与计算机间的联系,使得编程者并不理解计算机实际的工作机制,只能被编程语言限定在固定范围内,而C语言在这一点上做得恰到好处,其没有过度干预我们对计算机的操控,允许我们自由地控制内存、CPU。当然,C 语言也有很多不方便的地方,过于简单的接口使得很多操作不得不通过编写大量的代码来实现。

● Golang

并发是我对其最大的印象,我们可以用更容易理解的方式来实现并发,但是它的内存控制没有C语言那么方便、灵活。

● PHP

PHP的底层是C语言实现的,这也使得它继承了很多C语言的基因,PHP的简洁、易用、学习成本低等特点成就了它今天的地位。

PHP 的高度封装性与弱类型的特点使得很多操作极其简便,例如 JSON 的解析如果在Golang中完成,则需要定义一系列的结构体,然后才能完成解析,而在PHP中通过一行代码就可以完成。正是 PHP 底层的强大才得以实现如此简便的操作,那么强大的 PHP 背后到底是什么样子的呢?我想很多PHPer都有过这个疑问。然而让人感到沮丧的是,关于PHP内核的资料非常有限,已有的这些资料也不全面、系统,多数局限在理论介绍的层面上。后来我就直接去读PHP的源码,渐渐地发现,以前很多不理解的问题都在源码中找到了答案。本书主要的出发点是给那些想要了解 PHP 底层实现的读者一些启发,帮助更多的人理解 PHP 的实现,甚至能够参与到PHP的开发中,为未来PHP的发展贡献一份力量!

本书适合的对象

● 有一定C语言基础的读者。

● 想要理解PHP内部实现的读者。

● PHP高级工程师。

● 对虚拟机实现感兴趣的读者。

本书不适合作为PHP的入门教程。书中对于基础性的、概念性的东西介绍很少,重点是源码解析。

本书的结构

本书总共分为10章,章节之间存在一定的衔接,建议按照先后顺序阅读。其中第3~第9章为Zend引擎相关的内容,也是本书的核心章节。

第1章介绍PHP的基础内容。本章主要介绍PHP的历史发展、PHP7的主要变化,重点讲解PHP的构成部分与生命周期的几个阶段。

第2章介绍SAPI。本章选取了PHP 三种常见的应用场景,介绍三个不同 SAPI 的实现:Cli、Fpm、Embed。SAPI是PHP的接入层,如果只想了解Zend引擎的内容,那么可以跳过本章。

第3章介绍数据类型。本章主要介绍PHP中变量的基础结构zval,以及不同类型的结构,它们是 PHP 中最基础的、使用最频繁的数据结构,通过本章的内容你将了解 PHP 中变量的内部实现。

第4章介绍内存管理。本章主要介绍PHP变量自动回收机制的实现,以及PHP底层内存池、线程安全相关的实现。通过本章的内容,你将了解变量的内存是如何进行管理的,为什么PHP中的变量不需要手动申请释放。其中内存池的实现比较独立,它的实现与tcmalloc类似;线程安全只在多线程环境下使用,常见的Fpm、Cli模式不会用到,本书其他章节介绍的内容都是非线程安全的。

第5章介绍PHP的编译与执行。本章介绍PHP代码从编译到执行的整个过程,这也是Zend引擎的核心实现。通过对本章的学习,你将了解PHP代码是如何被Zend引擎识别、执行的。

第6章介绍函数的实现。本章介绍PHP中函数的实现,这也是Zend引擎的核心部分,本章的内容与第5章相关,介绍函数的编译与执行。

第7章介绍面向对象。本章介绍面向对象相关的实现,主要包括类、对象的内部实现。

第8章介绍命名空间。本章介绍PHP中命名空间的实现,这部分内容比较简单,命名空间只涉及编译阶段。

第9章介绍基础语法的实现。本章主要介绍PHP中基础语法的实现,比如条件分支、循环结构、中断跳转、静态变量、常量、全局变量、文件加载等,这些语法涉及PHP的编译、执行,它们是PHP语言的基础组成部分。通过对本章的学习,你可以更全面地掌握PHP语言的实现。

第10章介绍扩展开发。本章的内容偏向应用性,主要介绍扩展开发中常用的一些接口、宏。

勘误与支持

因个人水平有限,以及时间比较仓促,书中难免有不足之处,还望读者批评指正。如果你对本书有比较好的建议或对书中内容有所疑惑,可与我联系。

Email:pangudashu@gmail.com;QQ群:103330909

致谢

首先感谢PHP7的主要开发者鸟哥与PHP社区的其他开发者,正是他们的智慧造就了PHP,期待未来PHP能够有更加广阔的发展空间。在这里尤其要感谢Swoole的创始人韩天峰老师,本项目有幸得到韩老师的推荐,得到了众多人的关注。另外要单独感谢陈晓猛编辑,在他耐心地指导、审稿、修改工作下,最终才有了本书的诞生。

秦朋

读者服务

轻松注册成为博文视点社区用户(www.broadview.com.cn),扫码直达本书页面。

● 下载资源:本书如提供示例代码及资源文件,均可在下载资源处下载。

● 提交勘误:您对书中内容的修改意见可在提交勘误处提交,若被采纳,将获赠博文视点社区积分(在您购买电子书时,积分可用来抵扣相应金额)。

● 交流互动:在页面下方读者评论处留下您的疑问或观点,与我们和其他读者一同学习交流。

页面入口:http://www.broadview.com.cn/32810第1章PHP基础架构本章将简单介绍PHP的基本信息,以及PHP的安装、调试,同时将介绍PHP生命周期中的几个阶段,它们是PHP整个流程中比较关键的几个阶段。1.1简介

PHP是一种非常流行的高级脚本语言,尤其适合Web开发,快速、灵活和实用是PHP最重要的特点。PHP自1995年由Lerdorf创建以来,在全球得到了非常广泛的应用。

PHP在1995年早期以Personal Home Page Tools(PHP Tools)开始对外发表第一个版本,Lerdorf 写了一些介绍此程序的文档,并且发布了 PHP1.0。在这早期的版本中,提供了访客留言本、访客计数器等简单的功能,之后越来越多的网站开始使用PHP,并且强烈要求增加一些特性,在新的成员加入开发行列之后,Rasmus Lerdorf 在1995年6月8日将 PHP 公开发布,希望可以通过社群来加速程序开发与寻找错误。这个版本被命名为 PHP2,已经有了今日 PHP的一些雏型,类似Perl的变量命名方式、表单处理功能,以及嵌入到HTML中执行的能力。程序语法上也类似 Perl,有较多的限制,不过更简单、更有弹性。PHP/FI加入了对MySQL的支持,从此建立了PHP在动态网页开发上的地位。到了1996年年底,有15000个网站使用了PHP。

在 1997 年,任职于 Technion IIT 公司的两个以色列程序设计师 Zeev Suraski 和 Andi Gutmans重写了PHP的解析器,成为PHP3的基础,而 PHP 也在这个时候改称为PHP:Hypertext Preprocessor,1998年6月正式发布PHP3。Zeev Suraski和Andi Gutmans在PHP3发布后开始改写 PHP 的核心,这个在1999年发布的解析器被称为Zend Engine,他们也在以色列的 Ramat Gan 成立了 Zend Technologies 来管理 PHP 的开发。

在2000年5月22日,以Zend Engine 1.0为基础的PHP4正式发布。2004年7月13日发布了PHP5,PHP5则使用了第二代的Zend Engine。PHP包含了许多新特色:完全实现面向对象,引入PDO,以及许多性能方面的改进。目前PHP5.x仍然是应用非常广泛的一个版本。

PHP 独特的语法混合了C、Java、Perl及PHP自创新的语法,同时支持面向对象、面向过程,相比C、Java等语言具有语法简洁、使用灵活、开发效率高、容易学习等特点。

● 开源免费:PHP社群有大量活跃的开发者贡献代码。

● 快捷:程序开发快,运行快,技术本身学习快,实用性强。

● 效率高:PHP消耗相当少的系统资源,自动gc机制。

● 类库资源:有大量可用类库供开发者使用。

● 扩展性:允许用户使用C/C++扩展PHP。

● 跨平台:可以在UNIX、Windows、Mac OS等系统上使用PHP。

很多人认为PHP非常简单,没什么技术含量,这是非常片面的认识,任何语言都有其存在的价值、有其适合的应用领域,正是 PHP 底层的强大才造就了 PHP 语言的简洁、易用,这反而更能够提现出它的优秀所在。1.2安装及调试

在学习 PHP 内核之前,首先需要安装 PHP,以方便在学习过程中进行调试。本书使用的PHP 版本为 7.0.12,下载地址为 http://php.net/distributions/php-7.0.12.tar.gz,下载后使用以下命令进行编译、安装:

--enable-debug 参数为开启debug模式,方便我们进行调试。关于调试自然少不了gdb了,PHP内核的实现虽然比较复杂,但是阶段划分比较鲜明,可以通过gdb在各个阶段设置断点,然后进行相应的调试。学习内核时,可以使用 Cli 模式,因为它是单线程的,方便调试,这并不影响我们对内核的学习。同时,想要弄清楚 PHP 内核,自然少不了阅读 PHP 的源码,因此本书后面介绍的内容将会非常频繁地列举源码,而对于一些概念性的解释则点到为止。本书主要的目的是引导大家自己去阅读源码、探索PHP的实现,而不希望只简单地通过书中的描述来了解PHP,所以希望大家在阅读本书时,一定要准备好一份源码以便随时查看和调试。1.3PHP7的变化

PHP7 与 PHP5 版本相比有非常大的变化,尤其是在 Zend 引擎方面。为提升性能,PHP7对Zend进行了深度优化,使得PHP的运行速度大大提高,比PHP5.0~5.6快了近5倍,同时还降低了PHP对系统资源的占用。下面介绍PHP7比较大的几个变化。

1)抽象语法树

在 PHP 之前的版本中,PHP 代码在语法解析阶段直接生成了 ZendVM 指令,也就是在zend_language_parser.y中直接生成opline指令,这使得编译器与执行器耦合在一起。编译生成的指令供执行引擎使用,该指令是在语法解析时直接生成的,假如要把执行引擎换成别的,就需要修改语法解析规则;或者如果PHP的语法规则变了,但对应的执行指令没有变化,那么也需要对修改语法解析规则。

PHP7中增加了抽象语法树,首先是将PHP代码解析生成抽象语法树,然后将抽象语法树编译为ZendVM指令。抽象语法树的加入使得PHP的编译器与执行器很好地隔离开,编译器不需要关心指令的生成规则,然后执行器根据自己的规则将抽象语法树编译为对应的指令,执行器同样不需要关心该指令的语法规则是什么样子的。

2)Native TLS

开发过PHP5.x版本扩展的读者对TSRM_CC、TSRM_DC这两个宏一定不会陌生,它们是用于线程安全的。PHP中有很多变量需要在不同函数间共享,多线程的环境下不能简单地通过全局变量来实现,为了适应多线程的应用环境,PHP提供了一个线程安全资源管理器,将全局资源进行了线程隔离,不同的线程之间互不干扰。

使用全局资源需要先获取本线程的资源池,这个过程比较占用时间,因此,PHP5.x通过参数传递的方式将本线程的资源池传递给其他函数,避免重复查找。这种实现方式使得几乎所有的函数都需要加上接收资源池的参数,也就是TSRM_DC宏所加的参数,然后调用其他函数时再把这个参数传下去,不仅容易遗漏,而且这种方式极不优雅。

PHP7中使用Native TLS(线程局部存储)来保存线程的资源池,简单地讲就是通过__thread标识一个全局变量,这样这个全局变量就是线程独享的了,不同线程的修改不会相互影响。具体的实现在本书第4章会详细介绍。

3)指定函数参数、返回值类型

PHP7中可以指定函数参数及返回值的类型,例如:

这个函数的参数必须为字符串,返回值必须是数组,否则将会报error错误。

4)zval结构的变化

zval是PHP中非常重要的一个结构,它是PHP变量的内部结构,也是PHP内核中应用最为普遍的一个结构。在PHP5.x中,zval的结构是下面这个样子的:

type为类型,is_ref__gc标识该变量是否为引用,value为变量的具体值,它是一个union,用来适配不同的变量类型:

zval中还有一个比较重要的成员:refcount__gc,它记录变量的引用计数。引用计数是PHP实现变量自动回收的基础,也就是记录一个变量有多少个地方在使用的一种机制。PHP5.x中引用计数是在zval中而不是在具体的value中,这样一来,导致变量复制时需要复制两个结构,zval、zvalue_value始终绑定在一起。PHP7将引用计数转移到了具体的value中,这样更合理。因为zval只是变量的载体,可以简单地认为是变量名,而value才是真正的值,这个改变使得PHP变量之间的复制、传递更加简洁、易懂。除此之外,zval结构的大小也从24byte减少到了16byte,这是PHP7能够降低系统资源占用的一个优化点所在。关于变量的结构及引用计数机制的具体实现,本书将在第3章、第4章详细介绍。

5)异常处理

PHP5.x中很多操作会直接抛出error错误,PHP7中将多数错误改为了异常抛出,这样一来就可以通过try catch捕捉到,例如:

脚本中调用了一个不存在的函数,PHP5.x中报“PHP Fatal error:Call to undefined function test()”,而PHP7中可以通过Throwable异常类型进行捕获。新的异常处理方式使得错误处理更加可控。

6)HashTable的变化

HashTable,即哈希表,也被称为散列表,它是PHP中强大的array()类型的内部实现结构,也是内核中使用非常频繁的一个结构,函数符号表、类符号表、常量符号表等都是通过HashTable实现的。

PHP7中HashTable有非常大的变化,HashTable结构的大小从72byte减小到了56byte,同时,数组元素Bucket结构也从72byte减小到了32byte。新HashTable的实现在第3章时将详细说明。

7)执行器

execute_data、opline采用寄存器变量存储,执行器的调度函数为execute_ex(),这个函数负责执行PHP代码编译生成的ZendVM指令。在执行期间会频繁地用到execute_data、opline两个变量,在PHP5.x中,这两个变量是由execute_ex()通过参数传递给各指令handler的,在PHP7中不再采用传参的方式,而是将execute_data、opline通过寄存器来进行存储,避免了传参导致的频繁出入栈操作,同时,寄存器相比内存的访问速度更快。这个优化使得PHP的性能有了5%左右的提升,第5章将详细介绍这个特性。

8)新的参数解析方式

PHP5.x通过zend_parse_parameters()解析函数的参数,PHP7提供了另外一种方式,同时保留了原来的方式,但是新的解析方式速度更快,具体使用方式将在第10章介绍。

除了上面介绍的这些变化,PHP7中还有非常多的优化与新的特性,这里不再一一列举。本书介绍的主要内容是关于PHP7的内核实现,后面的章节介绍的内容不会过多地与PHP旧版本的实现进行比较。1.4PHP的构成

PHP的源码下有几个主要目录:SAPI、main、Zend、ext。其中SAPI是PHP的应用接口层;main为PHP的主要代码,主要是输入/输出、Web通信,以及PHP框架的初始化操作等,比如fastcgi协议的解析、扩展的加载、PHP配置的解析等工作都是由它来完成的,它位于ZendVM的上一层;Zend目录是PHP解析器的主要实现,即ZendVM,它是PHP语言的核心实现,PHP代码的解释、执行就是由Zend完成的;ext是PHP的扩展目录;TSRM为线程安全相关的实现。PHP各组成部分之间的关系如图1-1所示。图1-1 PHP的基本构成

1)SAPI

PHP是一个脚本解析器,提供脚本的解析与执行,它的输入是普通的文本,然后由PHP解析器按照预先定义好的语法规则进行解析执行。我们可以在不同环境中应用这个解析器,比如命令行下、Web环境中、嵌入其他应用中使用。为此,PHP提供了一个SAPI层以适配不同的应用环境,SAPI可以认为是PHP的宿主环境。SAPI也是整个PHP框架最外层的一部分,它主要负责PHP框架的初始化工作。如果SAPI是一个独立的应用程序,比如Cli、Fpm,那么main函数也将定义在SAPI中。SAPI的代码位于PHP源码的/sapi目录下,经常用到的两个SAPI是Cli、Fpm。

2)ZendVM

ZendVM是一个虚拟的计算机,它介于PHP应用与实际计算机中间,我们编写的PHP代码就是被它解释执行的。ZenVM 是 PHP 语言的核心实现,它主要由两部分组成:编译器、执行器。其中编译器负责将PHP代码解释为执行器可识别的指令,执行器负责执行编译器解释出指令。ZendVM的角色等价于Java中的JVM,它们都是抽象出来的虚拟计算机,与C/C++这类编译型语言不同,虚拟机上运行的指令并不是机器指令。虚拟机的一个突出优点是跨平台,只需要按照不同平台编译出对应的解析器就可以实现代码的跨平台执行。本书大部分章节介绍的内容都是关于ZendVM的,如果想要深入理解PHP,那么ZendVM就是最主要的目标了。

3)Extension

扩展是PHP内核提供的一套用于扩充PHP功能的一种方式,PHP社区中有丰富的扩展可供使用,这些扩展为PHP提供了大量实用的功能,PHP中很多操作的函数都是通过扩展提供的。通过扩展,我们可以使用C/C++实现更强大的功能和更高的性能,这也使得PHP与C/C++非常相近,甚至可以在C/C++应用中把PHP嵌入作为第三库使用。扩展分为PHP扩展、Zend扩展,PHP 扩展比较常见,而 Zend 扩展主要应用于 ZendVM,它可以做的东西更多,我们所熟知的Opcache就是Zend扩展。1.5生命周期

PHP的整个生命周期被划分为以下几个阶段:模块初始化阶段(module startup)、请求初始化阶段(request startup)、执行脚本阶段(execute script)、请求关闭阶段(request shutdown)、模块关闭阶段(module shutdown)。根据不同SAPI的实现,各阶段的执行情况会有一些差异,比如命令行模式下,每次执行一个脚本都会完整地经历这些阶段,而FastCgi模式下则在启动时执行一次模块初始化,然后各个请求只经历请求初始化、执行请求脚本、请求关闭几个阶段,在SAPI关闭时经历模块关闭阶段。各阶段执行的顺序与对应的处理函数如图1-2所示。图1-2 PHP的生命周期

1.模块初始化阶段

这个阶段主要进行 PHP 框架、Zend 引擎的初始化操作。该阶段的入口函数为php_module_startup(),如图1-3所示。这个阶段一般只在SAPI启动时执行一次,对于Fpm而言,就是在Fpm的master进程启动时执行的。图1-3 php_module_startup()

该阶段的几个主要处理如下所述。

● 激活SAPI:sapi_activate(),初始化请求信息SG(request_info)、设置读取POST请求的handler等,在module startup阶段处理完成后将调用sapi_deactivate()。

● 启动PHP输出:php_output_startup()。

● 初始化垃圾回收器:gc_globals_ctor(),分配zend_gc_globals内存。

● 启动Zend引擎:zend_startup(),主要操作包括:

启动内存池start_memory_manager();

设置一些util函数句柄(如zend_error_cb、zend_printf、zend_write等);

设置Zend虚拟机编译、执行器的函数句柄zend_compile_file、zend_execute_ex,以及垃圾回收的函数句柄gc_collect_cycles;

分配函数符号表(CG(function_table))、类符号表(CG(class_table))、常量符号表(EG(zend_constants))等,如果是多线程的话,还会分配编译器、执行器的全局变量;

注册Zend核心扩展:zend_startup_builtin_functions(),这个扩展是内核提供的,该过程将注册Zend核心扩展提供的函数,比如strlen、define、func_get_args、class_exists等;

注册Zend定义的标准常量:zend_register_standard_constants(),比如:E_ERROR、E_WARNING、E_ALL、TRUE、FALSE等;

注册$GLOBALS超全局变量的获取handler;

分配php.ini配置的存储符号表:EG(ini_directives)。

● 注册PHP定义的常量:PHP_VERSION、PHP_ZTS、PHP_SAPI,等等。

● 解析php.ini:解析完成后所有的php.ini配置保存在configuration_hash哈希表中。

● 映射PHP、Zend核心的php.ini配置:根据解析出的php.ini,获取对应的配置值,将最终的配置插入EG(ini_directives)哈希表。

● 注册用于获取$_GET、$_POST、$_COOKIE、$_SERVER、$_ENV、$_REQUEST、$_FILES变量的handler。

● 注册静态编译的扩展:php_register_internal_extensions_func()。

● 注册动态加载的扩展:php_ini_register_extensions(),将php.ini中配置的扩展加载到PHP中。

● 回调各扩展定义的module starup钩子函数,即通过PHP_MINIT_FUNCTION()定义的函数。

● 注册php.ini中禁用的函数、类:disable_functions、disable_classes。

2.请求初始化阶段

该阶段是在请求处理前每一个请求都会经历的一个阶段,对于 Fpm 而言,是在 worker 进程 accept 一个请求且读取、解析完请求数据后的一个阶段。该阶段的处理函数为 php_request_startup(),如图1-4所示。图1-4 php_request_startup()

主要的处理有以下几个。

● 激活输出:php_output_activate()。

● 激活Zend引擎:zend_activate(),主要操作如下所述。

重置垃圾回收器:gc_reset();

初始化编译器:init_compiler();

初始化执行器:init_executor(),将EG(function_table)、EG(class_table)分别指向CG(function_table)、CG(class_table),所以在PHP的编译、执行期间,

EG(function_table)与CG(function_table)、EG(class_table)与CG(class_table)是同一个值;另外还会初始化全局变量符号表EG(symbol_table)、include过的文件符号表EG(included_files);

初始化词法扫描器:startup_scanner()。

● 激活SAPI:sapi_activate()。

● 回调各扩展定义的request startup钩子函数:zend_activate_modules()。

3.执行脚本阶段

该阶段包括PHP代码的编译、执行两个核心阶段,这也是Zend引擎最重要的功能。在编译阶段,PHP脚本将经历从PHP源代码到抽象语法树再到opline指令的转化过程,最终生成的opline指令就是Zend引擎可识别的执行指令,这些指令接着被执行器执行,这就是PHP代码解释执行的过程,本书介绍的大部分内容都是关于这两个阶段的。这个接口的入口函数为php_execute_script(),如图1-5所示。图1-5 php_execute_script()

4.请求关闭阶段

在PHP脚本解释执行完成后将进入请求关闭阶段,这个阶段将flush输出内容、发送HTTP应答header头、清理全局变量、关闭编译器、关闭执行器等。另外,在该阶段将回调各扩展的request shutdown钩子函数。该阶段是请求初始化阶段的相反操作,与请求初始化时的处理一一对应,如图1-6所示。图1-6 php_request_shutdown()

5.模块关闭阶段

该阶段在SAPI关闭时执行,与模块初始化阶段对应,这个阶段主要进行资源的清理、PHP各模块的关闭操作,同时,将回调各扩展的 module shutdown 钩子函数。具体的处理函数为php_module_shutdown(),如图1-7所示。图1-7 php_module_shutdown()1.6小结

本章主要介绍了 PHP7 与旧版本的一些变化,以及学习 PHP7 内核前的准备工作,另外简单介绍了 PHP 生命周期的几个阶段。在接下来的章节中,我们将逐步揭开 PHP 内核的神秘面纱,一点点深入到PHP的内部实现中,引导大家深入理解PHP的实现。第2章SAPI这一章我们将首先介绍几个SAPI的实现,SAPI是PHP框架的接口层,它是进入PHP内部的入口。PHP中实现的SAPI有很多,本章选取了3个比较典型的SAPI:Cli、Fpm、Embed,分别介绍PHP在不同场景下的应用。其中Cli、Fpm SAPI是完整实现的应用程序,它们有定义自己的main函数,方便我们从入口开始逐步分析PHP的处理,尤其是单进程的Cli,非常方便调试,本书后面章节基本都是以Cli模式为例的。不同SAPI的实现尽管会有差异,但是它们都是围绕着PHP的生命周期实现的,在分析SAPI的实现时,我们将以PHP生命周期的几个阶段为主线,这样更有助于理解。尽管SAPI中并不涉及PHP具体的内部实现,但它是PHP框架最外层的接口,了解清楚它们的实现是探索PHP内核的第一步。2.1Cli

Cli(Command Line Interface),即命令行接口,用于在命令行下执行PHP脚本,就像Shell那样,它是执行PHP脚本最简便的一种方式。Cli SAPI最先是随PHP4.2.0版本发布的,但当时只是一个实验性的版本,需要在运行./configure 时加上--enable-cli 参数。从PHP4.3.0版本开始,Cli SAPI 成为了正式模块,--enable-cli参数会被默认设置为on,也可以用参数--disable-cli来屏蔽。

Cli模式通过执行编译的PHP 二进制程序即可启动,它定义了很多命令行参数,不同的参数对应不同的处理,比如:执行PHP脚本文件、直接执行PHP代码(-r参数)、输出PHP版本(-v参数)、输出已安装的扩展(-m参数)、指定php.ini配置(-c参数)……直接在PHP命令后加PHP脚本则将执行该脚本。2.1.1 执行流程

Cli是单进程模式,处理完请求后就直接关闭了,生命周期先后经历module startup、request startup、execute script、request shutdown、module shutdown,其执行流程比较简单,关键的处理过程如下:

Cli SAPI的main函数位于/sapi/cli/php_cli.c中,执行时首先解析命令行参数,然后初始化sapi_module_struct,从结构体名称可以看出,它是记录SAPI信息的主要结构,这个结构中有几个函数指针,它们是内核定义的操作接口的具体实现,用来告诉内核如何读取、输出数据。

具体的处理过程比较简单,这里不再展开。在完成参数的解析及sapi_module_struct的基本初始化后,接下来进入module startup阶段。

前面cli_sapi_module变量中定义的startup函数为php_cli_startup(),这个函数非常简单,直接调用了php_module_startup(),关于此函数的处理第1章已经介绍过,这里不再赘述。

在moduel startup阶段处理完成后,接下来进入请求初始化阶段:

do_cli()将完成请求的处理,此函数一开始对使用到的命令行参数进行解析,如果是一些查询系统信息之类的请求(如-v、-m、-i),则不需要经历PHP请求的生命周期,这里会单独处理,下面看一下执行PHP脚本请求时的处理。

PHP脚本执行时的输入形式有很多种,比如文件路径(filepath)、文件句柄(FILE)、文件描述符(fd)等,zend_file_handle结构就是用来定义不同输入形式的,这样可以统一PHP执行函数的输入参数。

Cli中此处使用的是文件句柄,在Linux环境下也就是调用fopen()打开一个文件,这样内核就可以直接读取PHP脚本代码了,当然也可以直接把文件路径提供给内核。定义好请求的输入结构后将进行请求初始化操作,即request startup阶段,然后开始PHP脚本的执行操作。

完成脚本的处理后进入request shutdown阶段:

do_cli()完成后回到main()函数中,进入module shutdown阶段,最后进程退出,这就是Cli下执行一个脚本的生命周期。2.1.2 内置Web服务器

从PHP5.4.0起,Cli SAPI 提供了一个内置的Web服务器,这个内置的Web服务器主要用于本地开发使用,不可用于线上产品环境。URI请求会被发送到PHP所在的的工作目录(Working Directory)进行处理,除非你使用了-t 参数来自定义不同的目录。

如果请求未指定执行哪个PHP文件,则默认执行目录内的index.php或者index.html。如果这两个文件都不存在,服务器会返回404错误。

当你在命令行启动这个Web Server时,如果指定了一个PHP文件,则这个文件会作为一个“路由”脚本,意味着每次请求都会先执行这个脚本。如果这个脚本返回 FALSE,那么直接返回请求的文件(例如请求静态文件不作任何处理)。

实际上,这个内置的Web服务器是一个独立的SAPI,它有自己的sapi_module_struct结构,也就是说Cli定义了两个SAPI。Cli的这个功能很少使用,所以其具体的实现这里不再展开。2.2Fpm

Fpm(FastCGI Process Manager)是PHP FastCGI运行模式的一个进程管理器,从它的定义可以看出,Fpm的核心功能是进程管理,那么它用来管理什么进程呢?这个问题需要从FastCGI说起。

FastCGI是Web服务器(如Nginx、Apache)和处理程序之间的一种通信协议,它是与HTTP类似的一种应用层通信协议。注意:它只是一种协议!

前面曾一再强调,PHP是一个脚本解析器,可以简单地把它理解为一个黑盒函数,输入是PHP脚本,输出是脚本的执行结果。除了在命令行下执行脚本,能不能让PHP处理HTTP请求呢?这种场景下就涉及网络处理,需要接收请求、解析协议,处理完成后再返回处理结果。在网络应用场景下,PHP并没有像Golang那样实现HTTP网络库,而是实现了FastCGI协议,然后与Web服务器配合实现了HTTP的处理,Web服务器来处理HTTP请求,然后将解析的结果再通过FastCGI协议转发给处理程序,处理程序处理完成后将结果返回给Web服务器,Web服务器再返回给用户,如图2-1所示。图2-1 FastCGI与Web服务器

PHP实现了FastCGI协议的处理,但是并没有实现具体的网络处理,比较常用的网络处理模型有以下两种。

● 多进程模型:由一个主进程和多个子进程组成,主进程负责管理子进程,基本的网络事件由各个子进程处理,Nginx采用的就是这种模型。

● 多线程模型:与多进程类似,只是它是线程粒度,这种模型通常会由主线程监听、接收请求,然后交由子线程处理,memcached就是这种模式;有的也是采用多进程那种模式—主线程只负责管理子线程不处理网络事件,各个子线程监听、接收、处理请求,memcached使用UDP协议时采用的是这种模式。

进程拥有独立的地址空间及资源,而线程则没有,线程之间共享进程的地址空间及资源,所以在资源管理上多进程模型比较简单,而多线程模型则需要考虑不同线程之间的资源冲突,也就是线程安全。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载