深入理解PHP内核(二)之SAPI探究


Posted in PHP onNovember 10, 2015

在上篇文章给大家介绍了深入了解PHP内核(一),相信大家通过本文多多少少都学到些知识吧,关于php内核知识继续关注本篇文章。

SAPI是Server Application Programming Interface(服务器应用编程接口)的缩写。PHP通过SAPI提供了一组接口,供应用和PHP内核之间进行数据交互。

简单的讲,就像函数的输入和输出一样,我们通过Linux命令行执行一段PHP代码,本质是Linux的Shell通过PHP的SAPI传入一组参数,Zend引擎执行后,返回给shell,由shell显示出来的过程。同样的,通过Apache调用PHP,通过Web服务器给SAPI传入数据,Zend引擎执行后,返回给Apache,由Apache显示在页面上。

深入理解PHP内核(二)之SAPI探究

图1. PHP架构图

PHP提供很多种形式的接口,包括apache、apache2filter、apache2handler、caudium、cgi 、cgi-fcgi、cli、cli-server、continuity、embed、isapi、litespeed、milter、nsapi、phttpd pi3web、roxen、thttpd、tux和webjames。但是常用的只有5种形式,CLI/CGI(命令行)、Multiprocess(多进程)、Multithreaded(多线程)、FastCGI和Embedded(内嵌)。

PHP提供了一个函数查看当前SAPI接口类型:

string php_sapi_name ( void )

PHP的运行和加载

无论使用哪种SAPI,在PHP执行脚本前后,都包含一系列事件:Module的Init(MINT)和Shutdown(MSHUTDOWN),Request 的Init(RINT)和Shutdown(RSHUTDOWN)。 第一阶段是PHP模块初始化阶段(MINT),可以初始化扩展内部变量、分配资源和注册资源处理器,在整个PHP实例生命周期内,该过程只执行一次。

什么是PHP模块?通过上面的PHP架构图,在PHP中可以使用get_loaded_extensions 函数来查看所有编译并加载的模块/扩展,相当于CLI模式下的php -m。

以PHP的Memcached扩展源代码为例:

PHP_MINIT_FUNCTION(memcached) { 
 zend_class_entry ce; 
 memcpy(&memcached_object_handlers,zend_get_std_object_handlers(), sizeof(zend_object_handlers)); 
memcached_object_handlers.clone_obj = NULL; /* 执行了一些类似的初始化操作 */ 
return SUCCESS; 
}

第二阶段是请求初始化阶段(RINT),在模块初始化并激活后,会创建PHP运行环境,同时调用所有模块注册的RINT函数,调用每个扩展的请求初始化函数 ,设定特定的环境变量、分配资源或执行其他任务,如审核。

PHP_RINIT_FUNCTION(memcached) { 
 /* 执行一些关于请求的初始化 */ 
 return SUCCESS; 
}

第三阶段,请求处理完成后,会调用PHP_RSHUTDOWN_FUNCTION进行回收,这是每个扩展的请求关闭函数,执行最后的清理工作。Zend引擎执行清理过程、垃圾收集、对之前的请求期间用到的每个变量执行unset。请求完成可能是执行到脚本完成,也可能是调用die()或exit()函数完成

第四阶段,当PHP生命周期结束时候,PHP_MSHUTDOWN_FUNCTION对模块进行回收处理,这是每个扩展的模块关闭函数,用于关闭自己的内核子系统。

PHP_MSHUTDOWN_FUNCTION(memcached) { /* 执行关于模块的销毁工作 */ UNREGISTER_INI_ENTRIES(); return SUCCESS; }

常见的运行模式

常见的SAPI模式有五种:

CLI和CGI模式(单进程模式)
多进程模式
多线程模式
FastCGI模式
嵌入式

1. CLI/CGI模式

CLI和CGI都属于单进程模式,PHP的生命周期在一次请求中完成。也就是说每次执行PHP脚本,都会执行第二部分讲的四个INT和Shutdown事件。

 深入理解PHP内核(二)之SAPI探究

图2. CGI/CLI生命周期

2. 多进程模式(Multiprocess)

多进程模式可以将PHP内置到Web Server中,PHP可以编译成Apache下的prefork MPM模式和APXS模块,当Apache启动后,会fork很多子进程,每个子进程拥有自己独立的进程地址空间。

  深入理解PHP内核(二)之SAPI探究

图3. 多进程模式生命周期

在一个子进程中,PHP的生命周期是调用MINT启动后,执行多次请求(RINT/RSHUTDOWN),在Apache关闭或进程结束后,才会调用MSHUTDOWN进行回收阶段。 
 

深入理解PHP内核(二)之SAPI探究

图4. 多进程的生命周期

多进程模型中,每个子进程都是独立运行,没有代码和数据共享,因此一个子进程终止退出和重新生成,不会影响其他子进程的稳定。

3. 多线程模式(Multithreaded)

Apache2的Worker MPM采用了多线程模型,在一个进程下创建多个线程,在同一个进程地址空间执行。

深入理解PHP内核(二)之SAPI探究

图5. 多线程生命周期

4. FastCGI模式

在我们用的Nginx+PHP-FPM用的就是FastCGI模式,Fastcgi是一种特殊的CGI模式,是一种常驻进程类型的CGI,运行后可以Fork多个进程,不用花费时间动态的Fork子进程,也不需要每次请求都调用MINT/MSHUTDOWN。PHP通过PHP-FPM来管理和调度FastCGI的进程池。Nginx和PHP-FPM通过本地的TCP Socket和Unix Socket 进行通信。

深入理解PHP内核(二)之SAPI探究

图6. FastCGI模式生命周期

PHP-FPM进程管理器自身初始化,启动多个CGI解释器进程等待来自Nginx的请求。当客户端请求达到PHP-FPM,管理器选择到一个CGI进程进行处理,Nginx将CGI环境变量和标准输入发送到一个PHP-CIG子进程。PHP-CGI子进程处理完成后,将标准输出和错误信息返回给Nginx,当PHP-CGI子进程关闭连接时,请求处理完成。PHP-CGI子进程等待着下一个连接。

可以想象CGI的系统开销有多大。每一个Web 请求PHP都必须重新解析php.ini、载入全部扩展并始化全部数据结构。使用FastCGI,所有这些都只在进程启动时发生一次。另外,对于数据库和Memcache的持续连接可以工作。

5. 内嵌模式(Embedded)

Embed SAPI是一种特殊的SAPI,允许在C/C++语言中调用PHP提供的函数。这种SAPI和CLI模式一样,按照Module Init => Request Init => Request => Request Shutdown => Module Shutdown的模式运行。

Embed SAPI可以调用PHP丰富的类库,也可以实现高级玩法,比如可以查看PHP的OPCODE(PHP执行的中间码,Zend引擎的指令,由PHP代码生成)。

详细请见: https://3water.com/article/74641.htm

SAPI的运行机制

我们以CGI为例,看一下SAPI的运行机制。

static sapi_module_struct cgi_sapi_module = { 
 "cgi-fcgi",   /* 输出给php_info()使用 */ "CGI/FastCGI",   /* pretty name */ 
 php_cgi_startup,  /* startup 当SAPI初始化时,首先会调用该函数 */ 
 php_module_shutdown_wrapper, /* shutdown 关闭函数包装器,它用来释放所有的SAPI的数据结构、内存等,调用php_module_shutdown */ 
 sapi_cgi_activate,  /* activate 此函数会在每个请求开始时调用,它会做初始化,资源分配 */ 
 sapi_cgi_deactivate,  /* deactivate 此函数会在每个请求结束时调用,它用来确保所有的数据都得到释放 */ 
 sapi_cgi_ub_write,  /* unbuffered write 不缓存的写操作(unbuffered write),它是用来向SAPI外部输出数据 */ 
 sapi_cgi_flush,   /* flush 刷新输出,在CLI模式下通过使用C语言的库函数fflush实现*/ NULL,    /* get uid */ 
 sapi_cgi_getenv,  /* getenv 根据name查找环境变量 */ 
 php_error,   /* error handler 注册错误处理函数 */ 
 NULL,    /* header handler PHP调用header()时候被调用 */ 
 sapi_cgi_send_headers,  /* send headers handler 发送头部信息*/ 
 NULL,    /* send header handler 发送一个单独的头部信息 */ 
 sapi_cgi_read_post,  /* read POST data 当请求的方法是POST时,程序获取POST数据,写入$_POST数组 */ 
 sapi_cgi_read_cookies,  /* read Cookies 获取Cookie值 */ 
 sapi_cgi_register_variables, /* register server variables 给$_SERVER添加环境变量 */ 
 sapi_cgi_log_message,  /* Log message 输出错误信息 */ 
 NULL,    /* Get request time */ 
 NULL,    /* Child terminate */ 
 STANDARD_SAPI_MODULE_PROPERTIES 
};

由上面代码可见,PHP的SAPI像是面向对象中基类,SAPI.h和SAPI.c包含的函数是抽象基类的声明和定义,各个服务器用的SAPI模式,则是继承了这个基类,并重新定义基类方法的子类。

总结

PHP的SAPI是Zend引擎提供的一组标准交互接口,通过注册初始化、析构、输入、输出等接口,我们可以将应用程序运行在Zend引擎上,也可以把PHP嵌入到类似Apache的Web Server中。PHP常见的SAPI模式有五种,CGI/CLI模式、多进程模式、多线程模式、FastCGI模式和内嵌模式。

了解PHP的SAPI机制意义重大,帮助我们理解PHP的生命周期,并了解如何更好的通过C/C++为PHP编写扩展,并在生命周期中找到提高系统性能的方式。

PHP 相关文章推荐
php 上传文件类型判断函数(避免上传漏洞 )
Jun 08 PHP
如何在symfony中导出为CSV文件中的数据
Oct 06 PHP
基于Snoopy的PHP近似完美获取网站编码的代码
Oct 23 PHP
Yii Framework框架获取分类下面的所有子类方法
Jun 20 PHP
php网页病毒清除类
Dec 08 PHP
thinkPHP实现将excel导入到数据库中的方法
Apr 22 PHP
PHP flush 函数使用注意事项
Aug 26 PHP
如何使用PHP给图片加水印
Oct 12 PHP
PHP第三方登录―QQ登录实现方法
Feb 06 PHP
thinkPHP微信分享接口JSSDK用法实例
Jul 07 PHP
PHP实现浏览器中直接输出图片的方法示例
Mar 14 PHP
浅析PHP 中move_uploaded_file 上传中文文件名失败
Apr 17 PHP
深入理解PHP内核(一)
Nov 10 #PHP
在PHP中使用FastCGI解析漏洞及修复方案
Nov 10 #PHP
PHP中使用GD库绘制折线图 折线统计图的绘制方法
Nov 09 #PHP
再推荐十款免费的php开发工具
Nov 09 #PHP
php开发工具有哪五款
Nov 09 #PHP
PHP编程开发怎么提高编程效率 提高PHP编程技术
Nov 09 #PHP
PHP 7的一些引人注目的新特性简单介绍
Nov 08 #PHP
You might like
第八节--访问方式
2006/11/16 PHP
一个PHP的QRcode类与大家分享
2011/11/13 PHP
php实现12306火车票余票查询和价格查询(12306火车票查询)
2014/01/14 PHP
PHP在网页中动态生成PDF文件详细教程
2014/07/05 PHP
php创建多级目录的方法
2015/03/24 PHP
PHP使用逆波兰式计算工资的方法
2015/07/29 PHP
PHP面向对象程序设计之接口的继承定义与用法详解
2018/12/20 PHP
innerHTML,outerHTML,innerTEXT三者之间的区别
2007/01/28 Javascript
js Flash插入函数免激活代码
2009/03/31 Javascript
JS/jQuery实现默认显示部分文字点击按钮显示全部内容
2013/05/13 Javascript
js实现select组件的选择输入过滤代码
2014/10/14 Javascript
js实现按钮加背景图片常用方法
2014/11/01 Javascript
【经典源码收藏】基于jQuery的项目常见函数封装集合
2016/06/07 Javascript
Bootstrap的modal拖动效果
2016/12/25 Javascript
EasyUI学习之DataGird分页显示数据
2016/12/29 Javascript
EasyUI为Numberbox添加blur事件的方法
2017/03/05 Javascript
jQuery基于事件控制实现点击显示内容下拉效果
2017/03/07 Javascript
nodejs入门教程六:express模块用法示例
2017/04/24 NodeJs
jQuery判断网页是否已经滚动到浏览器底部的实现方法
2017/10/27 jQuery
JavaScript 数组去重并统计重复元素出现的次数实例
2017/12/14 Javascript
vue项目上传Github预览的实现示例
2018/11/06 Javascript
js实现ATM机存取款功能
2020/10/27 Javascript
微信小程序传值以及获取值方法的详解
2019/04/29 Javascript
基于postman获取动态数据过程详解
2020/09/08 Javascript
Python多进程并发(multiprocessing)用法实例详解
2015/06/02 Python
Flask框架通过Flask_login实现用户登录功能示例
2018/07/17 Python
python里dict变成list实例方法
2019/06/26 Python
Django中使用session保持用户登陆连接的例子
2019/08/06 Python
在macOS上搭建python环境的实现方法
2019/08/13 Python
解决django model修改添加字段报错的问题
2019/11/18 Python
pyenv虚拟环境管理python多版本和软件库的方法
2019/12/26 Python
法国创作个性化T恤衫和其他定制产品平台:Tostadora
2018/04/08 全球购物
生日礼品店创业计划书范文
2014/03/21 职场文书
大学生自我评价200字(4篇)
2014/09/17 职场文书
2016年教师学习廉政准则心得体会
2016/01/20 职场文书
Mysql 用户权限管理实现
2021/05/25 MySQL