1、PHP的运行模式:

PHP两种运行模式是WEB模式、CLI模式。无论哪种模式,PHP工作原理都是一样的,作为一种SAPI运行。

1、当我们在终端敲入php这个命令的时候,它使用的是CLI。

它就像一个web服务器一样来支持php完成这个请求,请求完成后再重新把控制权交给终端。

2、当使用Apache或者别web服务器作为宿主时,当一个请求到来时,PHP会来支持完成这个请求。一般有:

PHP请求)

多线程模式

2、一切的开始: SAPI接口

通常我们编写php Web程序都是通过Apache或者Nginx这类Web服务器来测试脚本. 或者在命令行下通过php程序来执行PHP脚本. 执行完成脚本后,服务器应答,浏览器显示应答信息,或者在命令结束后在标准输出显示内容. 我们很少关心PHP解释器在哪里. 虽然通过Web服务器和命令行程序执行脚本看起来很不一样. 实际上她们的工作是一样的. 命令行程序和Web程序类似, 命令行参数传递给要执行的脚本,相当于通过url 请求一个PHP页面. 脚本戳里完成后返回响应结果,只不过命令行响应的结果是显示在终端上. 脚本执行的开始都是通过SAPI接口进行的.

        1)、启动apache:当给定的SAPI启动时,例如在对/usr/local/apache/bin/apachectl start的响应中,PHP由初始化其内核子系统开始。在接近启动例程的末尾,它加载每个扩展的代码并调用其模块初始化例程(MINIT)。这使得每个扩展可以初始化内部变量、分配资源、注册资源处理器,以及向ZE注册自己的函数,以便于脚本调用这其中的函数时候ZE知道执行哪些代码。

  2)、请求处理初始化:接下来,PHP等待SAPI层请求要处理的页面。对于CGI或CLI等SAPI,这将立刻发生且只发生一次。对于Apache、IIS或其他成熟的web服务器SAPI,每次远程用户请求页面时都将发生,因此重复很多次,也可能并发。不管请求如何产生,PHP开始于要求ZE建立脚本的运行环境,然后调用每个扩展的请求初始化 (RINIT)函数。RINIT使得扩展有机会设定特定的环境变量,根据请求分配资源,或者执行其他任务,如审核。 session扩展中有个RINIT作用的典型示例,如果启用了session.auto_start选项,RINIT将自动触发用户空间的session_start()函数以及预组装$_SESSION变量。

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

 4)、脚本结束:脚本运行结束后,PHP调用每个扩展的请求关闭(RSHUTDOWN)函数以执行最后的清理工作(如将session变量存入磁盘)。接下来,ZE执行清理过程(垃圾收集)-有效地对之前的请求期间用到的每个变量执行unset()。

  5)、sapi关闭:一旦完成,PHP继续等待SAPI的其他文档请求或者是关闭信号。对于CGI和CLI等SAPI,没有“下一个请求”,所以SAPI立刻开始关闭。关闭期间,PHP再次遍历每个扩展,调用其模块关闭(MSHUTDOWN)函数,并最终关闭自己的内核子系统。

简要的过程如下:

1. PHP是随着Apache的启动而运行的;
      2. PHP通过mod_php5.so模块和Apache相连(具体说来是SAPI,即服务器应用程序编程接口);
      3. PHP总共有三个模块:内核、Zend引擎、以及扩展层;
      4. PHP内核用来处理请求、文件流、错误处理等相关操作;
      5. Zend引擎(ZE)用以将源文件转换成机器语言,然后在虚拟机上运行它;
      6. 扩展层是一组函数、类库和流,PHP使用它们来执行一些特定的操作。比如,我们需要MySQL扩展来连接MySQL数据库
      7. 当ZE执行程序时可能会需要连接若干扩展,这时ZE将控制权交给扩展,等处理完特定任务后再返还;
      8. 最后,ZE将程序运行结果返回给PHP内核,它再将结果传送给SAPI层,最终输出到浏览器上。

3、PHP的开始和结束阶段

开始阶段有两个过程:

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

  1. PHP_MINIT_FUNCTION(myphpextension) { /* Initialize functions, classes etc */ }
  2. {
  3. // 注册常量或者类等初始化操作
  4. return SUCCESS;
  5. }

第二个过程发生在请求阶段,当一个页面请求发生时.则在每次请求之前都会进行初始化过程(RINIT请求开始).

请求到达之后,SAPI层将控制权交给PHP层,PHP初始化本次请求执行脚本所需的环境变量,例如创建一个执行环境,包括保存php运行过程中变量名称和变量值内容的符号表.
以及当前所有的函数以及类等信息的符号表.例如是Session模块的RINIT,如果在php.ini中启用了Session 模块,那在调用该模块的RINIT时就会初始化$_SESSION变量,并将相关内容读入;  然后PHP会调用所有模块RINIT函数,即“请求初始化”。 在这个阶段各个模块也可以执行一些相关的操作,
模块的RINIT函数和MINIT函数类似 ,RINIT方法可以看作是一个准备过程,在程序执行之间就会自动启动。

  1. PHP_RINIT_FUNCTION(myphpextension)
  2. {
  3. // 例如记录请求开始时间
  4. // 随后在请求结束的时候记录结束时间.这样我们就能够记录下处理请求所花费的时间了
  5. return SUCCESS;
  6. }

结束阶段分为两个环节:

请求处理完后就进入了结束阶段, 一般脚本执行到末尾或者通过调用exit()或者die()函数,PHP都将进入结束阶段. 和开始阶段对应,结束阶段也分为两个环节,一个在请求结束后(RSHUWDOWN),一个在SAPI生命周期结束时(MSHUTDOWN).
第一个环节:请求处理完后结束阶段:请求处理完后就进入了结束阶段,PHP就会启动清理程序。它会按顺序调用各个模块的RSHUTDOWN方法。
RSHUTDOWN用以清除程序运行时产生的符号表,也就是对每个变量调用unset函数。典型的RSHUTDOWN方法如下:
  1. PHP_RSHUTDOWN_FUNCTION(myphpextension)
  2. {
  3. // 例如记录请求结束时间, 并把相应的信息写入到日至文件中.
  4. return SUCCESS;
  5. }

第二个环节:最后,所有的请求都已处理完毕,SAPI也准备关闭了, PHP调用每个扩展的MSHUTDOWN方法,这是各个模块最后一次释放内存的机会。(这个是对于CGI和CLI等SAPI,没有“下一个请求”,所以SAPI立刻开始关闭。)

典型的RSHUTDOWN方法如下:

  1. PHP_MSHUTDOWN_FUNCTION(extension_name) {
  2. /* Free handlers and persistent memory etc */
  3. return SUCCESS;
  4. }

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

SAPI运行PHP都经过下面几个阶段:
       1、模块初始化阶段(Module init)     :
           即调用每个拓展源码中的的PHP_MINIT_FUNCTION中的方法初始化模块,进行一些模块所需变量的申请,内存分配等。
        2、请求初始化阶段(Request init)  :
           即接受到客户端的请求后调用每个拓展的PHP_RINIT_FUNCTION中的方法,初始化PHP脚本的执行环境。
        3、执行PHP脚本
        4、请求结束(Request Shutdown) 
          这时候调用每个拓展的PHP_RSHUTDOWN_FUNCTION方法清理请求现场,并且ZE开始回收变量和内存。
        5、关闭模块(Module shutdown)     :
           Web服务器退出或者命令行脚本执行完毕退出会调用拓展源码中的PHP_MSHUTDOWN_FUNCTION 方法

fork出多个子进程,每个进程的内存空间独立,每个子进程都会经过开始和结束环节, 不过每个进程的开始阶

被结束之后才会进行关闭阶段,在这两个阶段之间会随着每个请求重复请求开始-请求关闭的环节。

6、多线程的SAPI生命周期
多线程模式和多进程中的某个进程类似,不同的是在整个进程的生命周期内会并行的重复着 请求开始-请求关闭的环节.

在这种模式下,只有一个服务器进程在运行着,但会同时运行很多线程,这样可以减少一些资源开销,向Module init和Module
shutdown就只需要运行一遍就行了,一些全局变量也只需要初始化一次,因为线程独具的特质,使得各个请求之间方便的共享一些数据成为可能。

多线程工作方式如下图

7、Apache一般使用多进程模式prefork

Linux下使用#http –l 命令可以查看当前使用的工作模式。也可以使用#apachectl -l命令。
        看到的prefork.c,说明使用的prefork工作模式。

prefork 进程池模型,用在 UNIX
和类似的系统上比较多,主要是由于写起来方便,也容易移植,还不容易出问题。要知道,如果采用线程模型的话,用户线程、内核线程和混合型线程有不同的特性,移植起来就麻烦。prefork
模型,即预先 fork() 出来一些子进程缓冲一下,用一个锁来控制同步,连接到来了就放行一个子进程,让它去处理。

    prefork MPM 使用多个子进程,每个子进程只有一个线程。每个进程在某个确定的时间只能维持一个连接。在大多数平台上,Prefork MPM在效率上要比Worker MPM要高,但是内存使用大得多。prefork的无线程设计在某些情况下将比worker更有优势:他能够使用那些没有处理好线程安全的第三方模块,并 且对于那些线程调试困难的平台而言,他也更容易调试一些。

深入理解php底层:php生命周期的更多相关文章

  1. 【译】深入理解Rust中的生命周期

    原文标题:Understanding Rust Lifetimes 原文链接:https://medium.com/nearprotocol/understanding-rust-lifetimes- ...

  2. 理解React组件的生命周期

    本文作者写作的时间较早,所以里面会出现很多的旧版ES5的时代的方法.不过,虽然如此并不影响读者理解组件的生命周期.反而是作者分为几种不同的触发机制来解释生命周期的各个方法,让读者更加容易理解涉及到的概 ...

  3. 我所理解的 Laravel 请求 生命周期

    转载自:https://laravel-china.org/topics/3343/my-understanding-of-the-laravel-request-life-cycle 当你使用一个工 ...

  4. MVVM的理解和Vue的生命周期

    一.对于MVVM的理解? MVVM 是 Model-View-ViewModel 的缩写.Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑.View 代表UI 组件,它负责将数 ...

  5. JVM详解之:汇编角度理解本地变量的生命周期

    目录 简介 本地变量的生命周期 举例说明 优化的原因 总结 简介 java方法中定义的变量,它的生命周期是什么样的呢?是不是一定要等到方法结束,这个创建的对象才会被回收呢? 带着这个问题我们来看一下今 ...

  6. Vue父子组件生命周期执行顺序及钩子函数的个人理解

    先附一张官网上的vue实例的生命周期图,每个Vue实例在被创建的时候都需要经过一系列的初始化过程,例如需要设置数据监听,编译模板,将实例挂载到DOM并在数据变化时更新DOM等.同时在这个过程中也会运行 ...

  7. Facebook币Libra学习-2.交易生命周期

    交易生命周期 为了更加深入的理解Libra的交易生命周期,我们将跟随一个交易的全过程,从其被提交到Libra validator始,直至其被添加到区块链上止.我们将“放大”来看每个validator逻 ...

  8. flex 生命周期 ibm引用

    Flex 本质 提起 Flex 我们不得不追述其发展历史以及两个很重要的名词或者说技术,那就是 Flash 和 Flash Player.Flash 是 Adobe 推出的基于时间轴的交互式矢量图和 ...

  9. MEF 编程指南(九):部件生命周期

    理解 MEF 容器部件生命周期和实现是非常重要的事情.考虑到 MEF 关注可扩展应用程序.这变得尤为重要.生命期可以解释为期望部件的共享性(transitively, its exports)   共 ...

  10. Android studio教程:[5]活动的生命周期

    想要学好安卓开发,就必须理解安卓软件的生命周期,明白一个活动的创建.启动.停止.暂停.重启和销毁的过程,知道各个阶段会调用什么函数进行处理不同的情况,这里我就通过一个简单的例子让大家明白一个活动的生命 ...

随机推荐

  1. js实现一个简单钟表动画(javascript+html5 canvas)

    第一次在博客园注册发博.有一次去人家单位开标,看到开标网站上有个钟表动画,一时兴起,就写了个简单的钟表动画. 用js和html5 canvas对象实现一个简单钟表程序 主要用到的就是h5的canvas ...

  2. 新手入门Flume搭建部署

    在root的用户下 1):• 前提 安装JDK环境 2):• 在官网下载Flume,http://mirror.bit.edu.cn/apache/flume 既是:wget  http://mirr ...

  3. CSS fliter

    Filters主要是运用在图片上,以实现一些特效.(也能运用于video上),类似滤镜效果   img {         -webkit-filter:grayscale(1); }      gr ...

  4. PHP面向对象摘要

    一.面向对象的三种特性,分别是封装性,继承性和多态性. 1.封装性:封装是面向对象的核心思想,将对象的属性和行为封装起来,不需要让外界知道具体的实现细节,这就是封装思想. 2.继承性:继承性主要是描述 ...

  5. Python爬虫入门:Urllib库的高级使用

    1.设置Headers 有些网站不会同意程序直接用上面的方式进行访问,如果识别有问题,那么站点根本不会响应,所以为了完全模拟浏览器的工作,我们需要设置一些Headers 的属性. 首先,打开我们的浏览 ...

  6. 06.十分钟学会表达式语言EL

    一. 概念:MVC设计模式一个主要好处就是让jsp中的代码越来越来少,而且规定只能出现三种代码:接收属性,判断语句,迭代输出.但是,在开发中,jsp输出至少还是需要接受VO对象的,这时候为了避免导入V ...

  7. 几种常用的ajax 跨域请求

      前 言 首先,我们要明白,什么是跨域,为什么要跨域. 由于JS中存在同源策略.当请求不同协议名不同端口号下面的文件时,将会违背同源策略,无法请求成功!需要进行跨域处理! 这篇文章就为大家详细介绍一 ...

  8. JMeter基础教程3:脚本录制篇

    对于一些JMeter初学者来说,录制脚本可能是最容易掌握的技能之一.虽然我不建议录制性能脚本(因为录制的脚本比较混乱,必须要通过二次处理才可正常使用),但有时做总比不做要好,是吧?下面我们详细介绍使用 ...

  9. 【Arduino】2017年电子设计大赛B题 滚球控制系统|板球系统

    今年电赛我们队伍选择的是B题,滚球控制系统.最后我们得到了省特和国一,也算是了结了我大一时的心愿吧.下面对这次比赛进行一下总结,以后回忆起来的时候也有个念想. 滚球控制系统是一个多变量.非线性控制对象 ...

  10. MongoDB安装(windows 10环境)

    1. 下载 https://www.mongdb.com/ 选择版本:ws 2008 R2 64bit 2. 安装运行下载完成的MSI文件 安装时候选择自定义配置,选择安装路径,安装到E盘 等待2分钟 ...