深入理解PHP内核(二)之SAPI探究
在上篇文章给大家介绍了深入了解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显示在页面上。
图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事件。
图2. cgi/cli生命周期
2. 多进程模式(multiprocess)
多进程模式可以将php内置到web server中,php可以编译成apache下的prefork mpm模式和apxs模块,当apache启动后,会fork很多子进程,每个子进程拥有自己独立的进程地址空间。
图3. 多进程模式生命周期
在一个子进程中,php的生命周期是调用mint启动后,执行多次请求(rint/rshutdown),在apache关闭或进程结束后,才会调用mshutdown进行回收阶段。
图4. 多进程的生命周期
多进程模型中,每个子进程都是独立运行,没有代码和数据共享,因此一个子进程终止退出和重新生成,不会影响其他子进程的稳定。
3. 多线程模式(multithreaded)
apache2的worker mpm采用了多线程模型,在一个进程下创建多个线程,在同一个进程地址空间执行。
图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 进行通信。
图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代码生成)。
详细请见:
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编写扩展,并在生命周期中找到提高系统性能的方式。
上一篇: 编写Python CGI脚本的教程
下一篇: Yii框架页面渲染操作实例详解