PHP是怎么运行的
这篇文章,研究一下PHP代码是如何解释和执行以及PHP脚本运行的生命周期。
概述
PHP服务的启动。严格来说,PHP的相关进程是不需要手动启动的,它是随着Apache的启动而运行的。当然,如果有需要重启PHP服务的情况下也是可以手动重启PHP服务的。比如说在有开启opcode的正式环境更新了代码之后,需要重启PHP以重新编译PHP代码。
从宏观上来看,PHP内核的实现就是接收输入的数据,内部做相应的处理然后输出结果。对于PHP内核来说,我们编写的PHP代码就是内核接收的输入数据,PHP内核接收代码数据后,对我们编写的的代码进行代码解析和运算执行,最后返回相应的运算结果。
然而,不同于平时的C语言代码,要执行PHP代码,首先需要将PHP代码“翻译”成机器语言来执行相应的功能。而要执行“翻译”这个步骤,就需要PHP内核进行:词法分析、语法分析等步骤。最后交给PHP内核的Zend Engine进行顺次的执行。
词法分析
将PHP代码分隔成一个个的“单元”(TOKEN)
语法分析
将“单元”转换为Zend Engine可执行的操作
Zend Engine执行
对语法分析得到的操作进行顺次的执行
一切PHP程序(CGI/CLI)的开始都是从SAPI(Server Application Programming Interface)接口开始。SAPI指的是PHP具体应用的编程接口。例如Apache的mod_php。
PHP开始执行以后会经过两个主要的阶段:处理请求之前的开始阶段和请求之后的结束阶段。
开始阶段
PHP的整一个开始阶段会经历模块初始化和模块激活两个阶段。
MINIT
即模块初始化阶段,发生在Apache/Nginx启动以后的整个生命周期或者命令行程序整个执行过程中,此阶段只进行一次
RINIT
模块激活,发生在请求阶段。做一些初始化工作:如注册常量、定义模块使用的类等等
模块在实现时可以通过如下宏来实现这些回调函数:
PHP_MINIT_FUNCTION(myphpextension)
{
//注册常量或者类等初始化操作
return SUCCESS;
}
PHP_RINIT_FUNCTION(myphpextension)
{
//例如记录请求开始时间
//随后在请求结束的时候记录结束时间。这样我们就能够记录处理请求所花费时间了
return SUCCESS;
}
PHP脚本请求处理完就进入了结束阶段,一般脚本执行到末尾或者调用exit或die函数,PHP就进入结束阶段。
结束阶段
PHP的结束阶段分为停用模块和关闭模块两个环节。
RSHUTDOWN
停用模块(对应RINIT)
MSHUTDOWN
关闭模块(对应MINIT)
CLI/CGI模式的PHP属于单进程的SAPI模式。意思就是说,PHP脚本在执行一次之后就关闭掉,所有的变量和函数都不能继续使用。即在CGI模式下,同一个php文件的变量在其他php文件中不能使用。
下面用一个例子看看单线程PHP的SAPI生命周期。
单线程SAPI生命周期
如:
php -f test.php
调用各个扩展的MINIT 模块初始化
请求test.php
调用各个扩展的RINIT 模块激活
执行test.php
调用各个扩展的RSHUTDOWN 停用模块
执行完test.php后清理变量和内存
调用各个扩展的MSHUTDOWN 关闭模块
停止PHP执行
以上是一个简单的执行流程,下面做一些补充。
PHP在调用每个模块的模块初始化前,会有一个初始化的过程,包括:
初始化若干全局变量
大多数情况下是将其设置为NULL。
初始化若干常量
这里的常量是PHP自身的一些常量。
初始化Zend引擎和核心组件
这里的初始化操作包括内存管理初始化、全局使用的函数指针初始化,对PHP源文件进行词法分析、语法分析、中间代码执行的函数指针的赋值,初始化若干HashTable(比如函数表,常量表等等),为ini文件解析做准备,为PHP源文件解析做准备,注册内置函数、标准常量、GLOBALS全局变量等
解析php.ini
读取php.ini文件,设置配置参数,加载zend扩展并注册PHP扩展函数。
全局操作函数的初始化
初始化在用户空间所使用频率很高的一些全局变量,如:$\_GET、$\_POST、$\_FILES 等。
初始化静态构建的模块和共享模块(MINIT)
初始化默认加载的模块。
模块初始化执行操作:
将模块注册到已注册模块列表
将每个模块中包含的函数注册到函数表
禁用函数和类
会调用zend_disable_function函数将PHP的配置文件中的disable_functions变量代表的函数从CG(function_table)函数表中删除。
激活Zend引擎
使用init_compiler函数来初始化编译器。
激活SAPI
使用sapi_activate函数来初始化SG(sapi_headers)和SG(request_info),并且针对HTTP请求的方法设置一些内容。
环境初始化
初始化在用户控件需要用到的一些环境变量。包括服务器环境、请求数据环境等。
模块请求初始化
PHP调用zend_activate_modules函数遍历注册在module_registry变量中的所有模块,调用其RINIT方法方法实现模块的请求初始化操作。
在处理了文件相关的内容后,PHP会调用php_request_startup做请求初始化操作:
激活Zend引擎
激活SAPI
环境初始化
模块请求初始化
代码的运行
以上所有准备工作完成后,就开始执行PHP程序。PHP通过zend_compile_file做词法分析、语法分析和中间代码生成操作,返回此文件的所有中间代码。如果解析的文件有生成有效的中间代码,则调用zend_excute执行中间代码。。如果在执行过程中出现异常并且用户有定义对这些异常的处理,则调用这些异常处理函数。在所有的操作都处理完后,PHP通过EG(return_value_ptr_ptr)返回结果。
DEACTIVATION(关闭请求)
PHP关闭请求的过程是一个若干个关闭操作的集合,这个集合存在于php_request_shutdown函数中。这个包括:
调用所有通过register_shutdown_function()注册的函数。这些在关闭时调用的函数是在用户空间添加进来的。
执行所有可用的__destruct函数。这里的析构函数包括在对象池(EG(objects_store)中的所有对象的析构函数以及EG(symbol_table)中各个元素的析构方法。
将所有的输出刷出去。
发送HTTP应答头。
销毁全局变量表(PG(http_globals))的变量。
通过zend_deactivate函数,关闭词法分析器、语法分析器和中间代码执行器。
调用每个扩展的post-RSHUTDOWN函数。只是基本每个扩展的post_deactivate_func函数指针都是NULL。
关闭SAPI,通过sapi_deactivate销毁SG(sapi_headers)、SG(request_info)等的内容。
关闭流的包装器、关闭流的过滤器。
关闭内存管理。
重新设置最大执行时间
结束
PHP结束一个进程是,会调用sapi_flush函数将最后的内容刷新出去。然后调用zend_shutdown函数关闭Zend引擎。
参考:[http://www.php-internals.com/book/](http://www.php-internals.com/book/)
PHP是怎么运行的的更多相关文章
- ASP.NET Aries 入门开发教程1:框架下载与运行
背景: 鉴于框架的使用者越来越多,文档太少,不少用户反映框架的入门门槛太高. 好吧,再辛苦下,抽时间写教程吧! 步骤1:下载框架源码 开源地址:https://github.com/cyq1162/A ...
- 在传统.NET Framework 上运行ASP.NET Core项目
新的项目我们想用ASP.NET Core来开发,但是苦于我们历史的遗产很多,比如<使用 JavaScriptService 在.NET Core 里实现DES加密算法>,我们要估计等到.N ...
- Sublime Text3配置在可交互环境下运行python快捷键
安装插件 在Sublime Text3下面写代码感觉很不错,但是写Python的时候遇到了一些问题. 用Sublime Text3打开python文件,或者在Sublime Text3下写好pytho ...
- hadoop 2.7.3本地环境运行官方wordcount-基于HDFS
接上篇<hadoop 2.7.3本地环境运行官方wordcount>.继续在本地模式下测试,本次使用hdfs. 2 本地模式使用fs计数wodcount 上面是直接使用的是linux的文件 ...
- hadoop 2.7.3本地环境运行官方wordcount
hadoop 2.7.3本地环境运行官方wordcount 基本环境: 系统:win7 虚机环境:virtualBox 虚机:centos 7 hadoop版本:2.7.3 本次先以独立模式(本地模式 ...
- Docker笔记一:基于Docker容器构建并运行 nginx + php + mysql ( mariadb ) 服务环境
首先为什么要自己编写Dockerfile来构建 nginx.php.mariadb这三个镜像呢?一是希望更深入了解Dockerfile的使用,也就能初步了解docker镜像是如何被构建的:二是希望将来 ...
- Linux scp 设置nohup后台运行
Linux scp 设置nohup后台运行 1.正常执行scp命令 2.输入ctrl + z 暂停任务 3.bg将其放入后台 4.disown -h 将这个作业忽略HUP信号 5.测试会话中断,任务继 ...
- 在docker中运行ASP.NET Core Web API应用程序(附AWS Windows Server 2016 widt Container实战案例)
环境准备 1.亚马逊EC2 Windows Server 2016 with Container 2.Visual Studio 2015 Enterprise(Profresianal要装Updat ...
- Android数据存储之Android 6.0运行时权限下文件存储的思考
前言: 在我们做App开发的过程中基本上都会用到文件存储,所以文件存储对于我们来说是相当熟悉了,不过自从Android 6.0发布之后,基于运行时权限机制访问外置sdcard是需要动态申请权限,所以以 ...
- Android权限管理之Android 6.0运行时权限及解决办法
前言: 今天还是围绕着最近面试的一个热门话题Android 6.0权限适配来总结学习,其实Android 6.0权限适配我们公司是在今年5月份才开始做,算是比较晚的吧,不过现在Android 6.0以 ...
随机推荐
- zeromq rpc原型
/* Asynchronous request-reply single-threaded server in Python that spawns a request handler each ti ...
- [译]:Xamarin.Android平台功能——位置服务
返回索引目录 原文链接:Location Services. 译文链接:Xamarin.Android平台功能--位置服务 本部分介绍位置服务以及与如何使用位置提供商服务 Location Servi ...
- Puppet自动化部署-前期环境准备(2)
在安装Puppet环境之前需要配置好机器的基本配置,如规范网络地址IP.hostname,certname认证名称,ntp时间同步等配置完毕,完善的搭建自动化环境. 1.环境介绍 此处实现部署的环境是 ...
- Mesos高可用解决方案剖析
本文作者王勇桥,80后的IT攻城狮,供职于IBM多年,Mesos和Swarm社区的贡献者.本文是他根据自己对Mesos的高可用(High-Availability)设计方案的了解以及在Mesos社区贡 ...
- linux 负载均衡
[博文推荐]关于负载均衡技术使用的一些误区 如今,负载均衡已经不是一个新鲜的词,也不是什么新技术,主要用于解决单机负载能力的局限性,但问题是你的应用真的到了单 机的负载上限了吗,未必,很多不知道如何推 ...
- 将Unreal4打包后的工程嵌入到Qt或者桌面中
#include "widget.h" #include "ui_widget.h" #include "windows.h" #inclu ...
- BestCoder#49
Untitled Accepts: 504 Submissions: 1542 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/6 ...
- Spring AOP详解
一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址:http://www.cnbl ...
- 转:Webpack 指南(整理 草稿)
基础 安装 首先要安装 Node.js, Node.js 自带了软件包管理器 npm.用 npm 全局安装 Webpack: $ npm install webpack -g 通常我们会将 Webpa ...
- Codeforces Round #259 (Div. 2)
A. Little Pony and Crystal Mine 水题,每行D的个数为1,3.......n-2,n,n-2,.....3,1,然后打印即可 #include <iostream& ...