PHP架构剖析
一:PHP是什么
PHP(“PHP: Hypertext Preprocessor”,超文本预处理器的字母缩写)是一种被广泛应用的开放源代码的多用途脚本语言,它可嵌入到 HTML中,尤其适合 web 开发。
当我们查看PHP手册的时候,PHP给我们提供了大量的特性,大量的函数,供我们开发程序使用,以此来简化程序的开发。
二:PHP源码目录结构
我们先看看php的源码目录结构,从目录结构我们也可以窥见php底层的一些架构设计
- 根目录: /这个目录包含的东西比较多,主要包含一些说明文件以及设计方案。 其实项目中的这些README文件是非常值得阅读的例如:
- /README.PHP4-TO-PHP5-THIN-CHANGES 这个文件就详细列举了PHP4和PHP5的一些差异。
- 还有有一个比较重要的文件/CODING_STANDARDS,如果要想写PHP扩展的话,这个文件一定要阅读一下, 不管你个人的代码风格是什么样,怎么样使用缩进和花括号,既然来到了这样一个团体里就应该去适应这样的规范,这样在阅读代码或者别人阅读你的 代码是都会更轻松。
- build: 顾名思义,这里主要放置一些和源码编译相关的一些文件,比如开始构建之前的buildconf脚本等文件,还有一些检查环境的脚本等。
- ext :官方扩展目录,包括了绝大多数PHP的函数的定义和实现,如array系列,pdo系列,spl系列等函数的实现,都在这个目录中。个人写的扩展在测试时也可以放到这个目录,方便测试和调试。
- main :这里存放的就是PHP最为核心的文件了,主要实现PHP的基本设施,这里和Zend引擎不一样,Zend引擎主要实现语言最核心的语言运行环境。
- Zend: Zend引擎的实现目录,比如脚本的词法语法解析,opcode的执行以及扩展机制的实现等等。
- pear: “PHP 扩展与应用仓库”,包含PEAR的核心文件。
- sapi: 包含了各种服务器抽象层的代码,例如apache的mod_php,cgi,fastcgi以及fpm等等接口。
- TSRM :PHP的线程安全是构建在TSRM库之上的,PHP实现中常见的*G宏通常是对TSRM的封装,TSRM(Thread Safe Resource Manager)线程安全资源管理器。
- tests: PHP的测试脚本集合,包含PHP各项功能的测试文件
- win32: 这个目录主要包括Windows平台相关的一些实现,比如sokcet的实现在Windows下和*Nix平台就不太一样,同时也包括了Windows下编译PHP相关的脚本。
三:PHP的设计理念和特点
- 多进程模型:由于PHP是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,PHP也早已支持多线程模型。
- 弱类型语言:和C/C++、Java、C#等语言不同,PHP是一门弱类型语言。一个变量的类型并不是一开始就确定不变,运行中才会确定并可能发生隐式或显式的类型转换,这种机制的灵活性在web开发中非常方便、高效,具体会在后面PHP变量中详述。
- 引擎(Zend)+组件(ext)的模式降低内部耦合。
- 中间层(sapi)隔绝web server和PHP。
- 语法简单灵活,没有太多规范。缺点导致风格混杂,但再差的程序员也不会写出太离谱危害全局的程序。
四:PHP核心架构
PHP的核心架构图如下(来自网络)
从上图可以看出,PHP基本是一个4层的架构体系:
- Zend引擎:Zend整体用纯C实现,是PHP的内核部分,它将PHP代码翻译(词法、语法解析等一系列编译过程)为可执行opcode的处理并实现相应的处理方法、实现了基本的数据结构(如hashtable、oo)、内存分配及管理、提供了相应的api方法供外部调用,是一切的核心,所有的外围功能均围绕Zend实现。
- Extensions:围绕着Zend引擎,extensions通过组件式的方式提供各种基础服务,我们常见的各种内置函数(如array系列)、标准库等都是通过extension来实现,用户也可以根据需要实现自己的extension以达到功能扩展、性能优化等目的(如贴吧正在使用的PHP中间层、富文本解析就是extension的典型应用)。
- SAPI:SAPI提供了一个和外部通信的接口。Sapi全称是Server Application Programming Interface,也就是服务端应用编程接口,Sapi通过一系列钩子函数,使得PHP可以和外围交互数据,这是PHP非常优雅和成功的一个设计,通过sapi成功的将PHP本身和上层应用解耦隔离,PHP可以不再考虑如何针对不同应用进行兼容,而应用本身也可以针对自己的特点实现不同的处理方式。对于PHP,默认提供很多SAPI,比如给apache的mod_php5,CGI,还有sehll的CLI等
- 上层应用:这就是我们平时编写的PHP程序,通过不同的sapi方式得到各种各样的应用模式,如通过webserver实现web应用、在命令行下以脚本方式运行等等。
Zend引擎
Zend引擎是PHP实现的核心,提供了语言实现上的基础设施。
例如:PHP的语法实现,脚本的编译运行环境, 扩展机制以及内存管理等。
目前PHP的实现和Zend引擎之间的关系非常紧密,甚至有些过于紧密了,例如很多PHP扩展都是使用的Zend API, 而Zend正是PHP语言本身的实现,PHP只是使用Zend这个内核来构建PHP语言的,而PHP扩展大都使用Zend API, 这就导致PHP的很多扩展和Zend引擎耦合在一起了。
zend虚拟机体系图:
SAPI
在其生命周期的各个阶段,一些与服务相关的操作都是通过SAPI接口实现。 这些内置实现的物理位置在PHP源码的SAPI目录。这个目录存放了PHP对各个服务器抽象层的代码, 例如命令行程序的实现,Apache的mod_php模块实现以及fastcgi的实现等等。
在各个服务器抽象层之间遵守着相同的约定,这里我们称之为SAPI接口。 每个SAPI实现都是一个_sapi_module_struct结构体变量。(SAPI接口)。 在PHP的源码中,当需要调用服务器相关信息时,全部通过SAPI接口中对应方法调用实现, 而这对应的方法在各个服务器抽象层实现时都会有各自的实现。
sapi的简单示意图:
Extensions
如果php提供的各种功能还不能适合你的应用,那么你也可以自己开发扩展来满足你的需求。
我们常见的各种内置函数(如array系列)、标准库等都是通过extension来实现,用户也可以根据需要实现自己的extension以达到功能扩展
上层应用
就是我们自己写的PHP程序
五:PHP的生命周期
PHP程序的启动有2个概念上的启动,终止也有2个概念上的终止。
其中一个是PHP作为Apache(拿Apache举例)的一个模块的启动和终止,这次启动会初始化一些必要的数据,比如与宿主Apache有关的,并且这些书是常驻内存的。终止与之相对。
还有一个概念上的启动就是当Apache分配一个页面请求过来的时候,PHP会有一次启动和终止,这是我们常讨论的一种。
PHP扩展的生命旅程所执行的4个过程:
1、 在最初的初始化时候,就是PHP随着Apache启动而诞生在内存的时候,它会把自己所有的扩展的MINIT方法(全称Module Initialization,是由每个模块自己定义的函数)都执行一遍。在这个时间里,扩展可以定义一些自己的常量,类,资源等所有会被用户的的PHP脚本用到的东西。请记住:这里定义的东西都会随Apache常驻内存,可以被所有请求使用,知道Apache卸载掉PHP模块!
内核中预置了PHP_MINIT_FUNCTION宏函数,来帮助我们实现这个功能。
2、当一个页面请求到来时候,PHP会迅速的开辟一个新的环境,并重新扫描自己的各个扩展, 遍历执行它们
各自的RINIT方法(俗称Request Initialization), 这时候一个扩展可能会初始化在本次请求中会使用到的变量
等, 还会初始化等会儿用户端(即PHP脚本)中的变量之类的。
内核预置了PHP_RINIT_FUNCTION()这个宏函数来帮我们实现这个功能。
3、好了,现在这个页面请求执行的差不多了,可能是顺利的走到了自己文件的最后, 也可能是出师未捷,半
道被用户给die或者exit了, 这时候PHP便会启动回收程序,收拾这个请求留下的烂摊子。 它这次会执行所
有已加载扩展的RSHUTDOWN(俗称Request Shutdown)方法, 这时候扩展可以抓紧利用内核中的变量
表之类的做一些事情, 因为一旦PHP把所有扩展的RSHUTDOWN方法执行完, 便会释放掉这次请求使用
过的所有东西, 包括变量表的所有变量、所有在这次请求中申请的内存等等。
内核预置了PHP_RSHUTDOWN_FUNCTION宏函数来帮助我们实现这个功能
4、前面该启动的也启动了,该结束的也结束了,现在该Apache老人家歇歇(Apache停止)的时候,当Apache通知PHP自己要Stop的时候,PHP便进入MSHUTDOWN(俗称Module Shutdown)阶段。这时候PHP便会给所有扩展下最后通牒,如果哪个扩展还有未了的心愿,就放在自己MSHUTDOWN方法里,这可是最后的机会了,一旦PHP把扩展的MSHUTDOWN执行完,便会进入自毁程序,这里一定要把自己擅自申请的内存给释放掉,否则就杯具了。
内核中预置了PHP_MSHUTDOWN_FUNCTION宏函数来帮助我们实现这个功能
PHP生命周期
前面我们说了PHP扩展执行的4个过程,依次进行Module init、Request init、Request Shutdown、Module shutdown四个过程,当然之间还会执行脚本自己的逻辑。 那么两种init和两种shutdown各会执行多少次、各自的执行频率有多少呢? 这取决与PHP是用什么sapi与宿主通信的。最常见的四种方式如下所列:
- 直接以CLI/CGI模式调用
- 多进程模式
- 多线程模式
- Embedded(嵌入式,在自己的C程序中调用Zend Engine)
1、 CLI/CIG
CLI和CGI的SAPI是相当特殊的,因为这时PHP的生命周期完全在一个单独的请求中完成。虽然简单,不过
我们以前提过的两种init和两种shutdown仍然都会被执行。
2.、多进程模式
因为是fork出来的,所以各个进程间的数据是彼此独立,不会受到外界的干扰(ps:fork后可以用管道等方
式实现进程间通信)。 这是一片独立天地,它允许每个子进程做任何事情,玩七十码、躲猫猫都没人管,办
公室拿砍刀玩自杀也没事, 下图展示了从apache的视角来看多进程工作模式下的PHP:
3、多线程模式
随着时代的进步,PHP越来越多的在多线程模式下工作,就像IIS的isapi和Apache MPM worker(支持混合
的多线程多进程的多路处理模块)。 在这种模式下,只有一个服务器进程在运行着,但会同时运行很多线程,这样可以减少一些资源开销, 像Module init和Module shutdown就只需要运行一次就行了,一些全局
变量也只需要初始化一次, 因为线程独具的特质,使得各个请求之间方便的共享一些数据成为可能。
4、Embed
Embed SAPI是一种比较特殊的sapi,容许你在C/C++语言中调用PHP/ZE提供的函数。 并且这种sapi和上
面的三种一样,按Module Init、Request Init、Rshutdown、mshutdown的流程执行着。 当然,这只是其中
一种情况。因为特定的应用有自己特殊的需求,只是在处理PHP脚本这个环节基本一致。
参考:
1.http://www.laruence.com/2008/08/12/180.html 深入理解Zend SAPIs(Zend SAPI Internals)
2.http://www.php-internals.com/book/?p=chapt02/02-01-php-life-cycle-and-zend-engine 生命周期和Zend引擎
3.http://www.laruence.com/2008/08/03/201.html 揭秘TSRM(Introspecting TSRM)