PHP运行流程图解

原创
2019/02/27 16:46
阅读数 144
  • CGI
  1. 概念:CGI是common gateway interface的缩写,译作通用网关接口,但很不幸,我们无法见名知意,我们知道,web服务器所处理的内容都是静态的,要想处理动态内容,需要依赖于web应用程序,如php、jsp、python、perl等。但是web server如何将动态的请求传递给这些应用程序?它所依赖的就是cgi协议。没错,是协议,也就是web server和web应用程序交流时的规范。换句话说,通过cgi协议,再结合已搭建好的web应用程序,就可以让web server也能"处理"动态请求,你肯定知道处理两字为什么要加上双引号。
  2. 它是一种协议。通过cgi协议,web server可以将动态请求和相关参数发送给专门处理动态内容的应用程序。
  3. CGI工作原理:CGI方式在遇到连接请求(用户请求)先要创建cgi的子进程,激活一个CGI进程,然后处理请求,处理完后结束这个子进程。这就是fork-and-execute模式。所以用cgi方式的服务器有多少连接请求就会有多少cgi子进程,子进程反复加载是cgi性能低下的主要原因。当用户请求数量非常多时,会大量挤占系统的资源如内存,CPU时间等,造成效能低下。
  • FASTCGI
  1. 概念:也是一种协议,只不过是cgi的优化版。cgi的性能较烂,fastcgi则在其基础上进行了改进。FastCGI技术目前支持语言有:C/C++、Java、Perl、Tcl、Python、SmallTalk、Ruby等
  2. 工作原理:fast-cgi是cgi的升级版本,是一个进程可以处理多个请求,和上面的cgi协议完全不一样,cgi是一个进程只能处理一个请求,这样就会导致大量的cgi程序,因此会给服务器带来负担。FastCGI像是一个常驻型的CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去fork一次
  • PHP-CGI
  1. 概念:PHP-CGI是PHP自带的FastCGI管理器。
  2. 工作原理:fastcgi是一种协议,而php-cgi实现了这种协议。不过这种实现比较烂。它是单进程的,一个进程处理一个请求,处理结束后进程就销毁。
  • PHP-FPM:
  1. 概念:PHP-FPM是一个PHP FastCGI管理器,是只用于PHP的是对php-cgi的改进版,它直接管理多个php-cgi进程/线程。也就是说,php-fpm是php-cgi的进程管理器因此它也算是fastcgi协议的实现。
  2. 工作原理:php-fpm会开启多个php-cgi程序,并且php-fpm常驻内存,每次web serve服务器发送连接过来的时候,php-fpm将连接信息分配给下面其中的一个子程序php-cgi进行处理,处理完毕这个php-cgi并不会关闭,而是继续等待下一个连接(一个进程至少服务上万次请求才退出),这也是fast-cgi加速的原理,但是由于php-fpm是多进程的,而一个php-cgi基本消耗7-25M内存,因此如果连接过多就会导致内存消耗过大,引发一些问题,例如nginx里的502错误。
  3. 为什么一定要退出:因为担心RINIT->RSHUTDOWN循环,有哪个代码写的不好,变量一直没释放,内存泄露GC又回收不了。php-fpm里的pm.max_requests配置就是设置RINT循环多少次,退出进程。
  4. 同时php-fpm还附带一些其他的功能:例如平滑过渡配置更改,普通的php-cgi在每次更改配置后,需要重新启动才能初始化新的配置,而php-fpm是不需要,php-fpm分将新的连接发送给新的子程序php-cgi,这个时候加载的是新的配置,而原先正在运行的php-cgi还是使用的原先的配置,等到这个连接下一次连接的时候会使用新的配置初始化,这就是平滑过渡。
  • CGI进程/线程:在php上,就是php-cgi进程/线程。专门用于接收web server的动态请求,调用并初始化zend虚拟机
  • CGI脚本:被执行的php源代码文件。
  • ZEND虚拟机:对php文件做词法分析、语法分析、编译成opcode,并执行。最后关闭zend虚拟机。
  • CGI进程/线程和ZEND虚拟机的关系:cgi进程调用并初始化zend虚拟机的各种环境。
  • 常见的SAPI:SAPI提供了一个和外部通信的接口,常见的SAPI有:cgi 、fast-cgi、cli、isapi、apache 模块的 DLL
  • PHP的组成
  1. PHP总共有三个模块:内核、Zend引擎、以及扩展层。
  2. PHP内核用来处理请求、文件流、错误处理等相关操作。
  3. Zend引擎用以将源文件转换成机器语言,然后在虚拟机上运行它。
  4. 扩展层是一组函数、类库和流,PHP使用它们来执行一些特定的操作。

比如,我们需要mysql扩展来连接MySQL数据库; 当ZE执行程序时可能会需要连接若干扩展,这时ZE将控制权交给扩展,等处理完特定任务后再返还;最后,ZE将程序运行结果返回给PHP内核,它再将结果传送给SAPI层,最终输出到浏览器上。

  • WEB-SERVER和PHP-CGI的交互模式

web server和php-cgi有3种交互模式。

  1. cgi模式:httpd接收到一个动态请求就fork一个cgi进程,cgi进程返回结果给httpd进程后自我销毁。
  2. 动态模块模式:将php-cgi的模块编译进httpd(Apache的方法)。在httpd启动时会加载模块,加载时也将对应的模块激活,php-cgi也就启动了。(注:纠正一个小小错误,很多人以为动态编译的模块是可以在需要的时候随时加载调用,不需要的时候它们就停止了,实际上不是这样的。和静态编译的模块一样,动态加载的模块在被加载时就被加入到激活链表中,无论是否使用它,它都已经运行在apache httpd的内部)
  3. php-fpm模式:使用php-fpm管理php-cgi,此时httpd不再控制php-cgi进程的启动。可以将php-fpm独立运行在非web服务器上,实现所谓的动静分离。
  • PHP开始执行以后会经过两个主要的阶段:处理请求之前的开始阶段和请求之后的结束阶段

由上面的图,我们能知道,SAPI运行PHP都经过下面几个阶段

请求处理之前就进入了开始阶段, 分为两个环节,一个是模块初始化阶段(Module init),一个是请求初始化阶段(Request init)

  • 模块初始化阶段(Module init):即调用每个拓展源码中的的PHP\_MINIT\_FUNCTION中的方法初始化模块,进行一些模块所需变量的申请,内存分配

模块初始化阶段MINIT:在整个SAPI生命周期内(例如Apache启动以后的整个生命周期内或者命令行程序整个执行过程中),该过程只进行一次。第一步的操作在任何请求到达之前就发生了。PHP调用各个扩展的MINIT方法,从而使这些扩展切换到可用状态。我们可以看看php.ini文件里打开了哪些扩展,这些扩展会在这个阶段进行”模块初始化“。 MINIT的意思是“模块初始化”。各个模块都定义了一组函数、类库等用以处理其他请求。启动Apache后,PHP解释程序也随之启动;PHP调用各个扩展(模块)的MINIT方法,从而使这些扩展切换到可用状态。

  • 请求初始化阶段(Request init):即接受到客户端的请求后调用每个拓展的PHP\_RINIT\_FUNCTION中的方法,初始化PHP脚本的执行环境。

 

该过程发生在请求阶段, 例如通过url请求某个页面,则在每次请求之前都会进行模块激活(RINIT请求开始)。请求到达之后,SAPI层将控制权交给PHP层,PHP初始化本次请求执行脚本所需的环境变量。例如是Session模块的RINIT,如果在php.ini中启用了Session 模块,那在调用该模块的RINIT时就会初始化$_SESSION变量,并将相关内容读入; 然后PHP会调用所有模块RINIT函数,即“请求初始化”。在这个阶段各个模块也可以执行一些相关的操作, 模块的RINIT函数和MINIT函数类似 ,RINIT方法可以看作是一个准备过程,在程序执行之前就会自动启动。

  • 运行阶段-》执行PHP脚本(这一步,应该是大多数PHP程序员所熟悉的部分,自己写的代码就是在这里执行的)

一旦请求被初始化了,ZE开始接管控制权,将PHP脚本翻译成符号(OPcode),最终形成操作码并逐步运行之。如任一操作码需要调用扩展的函数,ZE将会把参数绑定到该函数,并且临时交出控制权直到函数运行结束。

  1. scanner:将PHP代码转换为Tokens,详见代码Zend/zend\_language\_scanner.l。
  2. parser:将Tokens转换成表达式,详见代码Zend/zend\_language\_parser.y。
  3. compile:将表达式编译成opcode。opcode存放在op\_array中。
  4. execute:Zend Engine调用zend\_execute来执行op\_array,输出结果。

请求处理完后就进入了结束阶段, 一般脚本执行到末尾或者通过调用exit()或者die()函数,PHP都将进入结束阶段. 和开始阶段对应,结束阶段也分为两个环节,一个在请求结束后(RSHUWDOWN),一个在SAPI生命周期结束时(MSHUTDOWN)。

  • 请求结束(Request Shutdown):这时候调用每个拓展的PHP\_RSHUTDOWN\_FUNCTION方法清理请求现场,并且ZE开始回收变量和内存。

请求处理完后就进入了结束阶段,PHP就会启动清理程序。它会按顺序调用各个模块的RSHUTDOWN方法。RSHUTDOWN用以清除程序运行时产生的符号表,也就是对每个变量调用unset函数。

  • 关闭模块(Module shutdown): Web服务器退出或者命令行脚本执行完毕退出会调用拓展源码中的PHP\_MSHUTDOWN\_FUNCTION 方法。

    最后,所有的请求都已处理完毕,SAPI也准备关闭了,PHP调用每个扩展的MSHUTDOWN方法,这是各个模块最后一次释放内存的机会。

    (这个是对于CGI和CLI等SAPI,没有“下一个请求”,所以SAPI立刻开始关闭。)

到此整个PHP生命周期就结束了。要注意的是,只有在服务器没有请求的情况下才会执行“启动第一步”和“关闭第二步”。

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部