php原理全面剖析

从SAPI接口开始 php


SAPI:Server Application Programming Interface 服务器端应用编程端口。研究过PHP架构的同窗应该知道这个东东的重要性,它提供了一个接口,使得PHP能够和其余应用进行交互数据。 本文不会详细介绍每一个PHP的SAPI,只是针对最简单的CGI SAPI,来讲明SAPI的机制。 html

咱们先来看看PHP的架构图: 前端

SAPI指的是PHP具体应用的编程接口, 就像PC同样,不管安装哪些操做系统,只要知足了PC的接口规范均可以在PC上正常运行, PHP脚本要执行有不少种方式,经过Web服务器,或者直接在命令行下,也能够嵌入在其余程序中。 mysql

一般,咱们使用Apache或者Nginx这类Web服务器来测试PHP脚本,或者在命令行下经过PHP解释器程序来执行。 脚本执行完后,Web服务器应答,浏览器显示应答信息,或者在命令行标准输出上显示内容。 nginx

咱们不多关心PHP解释器在哪里。虽然经过Web服务器和命令行程序执行脚本看起来很不同, 实际上它们的工做流程是同样的。命令行参数传递给PHP解释器要执行的脚本, 至关于经过url请求一个PHP页面。脚本执行完成后返回响应结果,只不过命令行的响应结果是显示在终端上。 web

脚本执行的开始都是以SAPI接口实现开始的。只是不一样的SAPI接口实现会完成他们特定的工做, 例如Apache的mod_php SAPI实现须要初始化从Apache获取的一些信息,在输出内容是将内容返回给Apache, 其余的SAPI实现也相似。 sql

SAPI提供了一个和外部通讯的接口, 对于PHP5.2,默认提供了不少种SAPI, 常见的给apache的mod_php5,CGI,给IIS的ISAPI,还有Shell的CLI,本文就从CGI SAPI入手 ,介绍SAPI的机制。 虽然CGI简单,可是不用担忧,它包含了绝大部份内容,足以让你深入理解SAPI的工做原理。 数据库

要定义个SAPI,首先要定义个sapi_module_struct, 查看 PHP-SRC/sapi/cgi/cgi_main.c: apache

01 */
02 staticsapi_module_struct cgi_sapi_module = {
03 #if PHP_FASTCGI
04     "cgi-fcgi",                    /* name */
05     "CGI/FastCGI",                 /* pretty name */
06 #else
07     "cgi",                         /* name */
08     "CGI",                         /* pretty name */
09 #endif
10   
11     php_cgi_startup,               /* startup */
12     php_module_shutdown_wrapper,   /* shutdown */
13   
14     NULL,                          /* activate */
15     sapi_cgi_deactivate,           /* deactivate */
16   
17     sapi_cgibin_ub_write,          /* unbuffered write */
18     sapi_cgibin_flush,             /* flush */
19     NULL,                          /* get uid */
20     sapi_cgibin_getenv,            /* getenv */
21   
22     php_error,                     /* error handler */
23   
24     NULL,                          /* header handler */
25     sapi_cgi_send_headers,         /* send headers handler */
26     NULL,                          /* send header handler */
27   
28     sapi_cgi_read_post,            /* read POST data */
29     sapi_cgi_read_cookies,         /* read Cookies */
30   
31     sapi_cgi_register_variables,   /* register server variables */
32     sapi_cgi_log_message,          /* Log message */
33     NULL,                          /* Get request time */
34   
35     STANDARD_SAPI_MODULE_PROPERTIES
36 };

这个结构,包含了一些常量,好比name, 这个会在咱们调用php_info()的时候被使用。一些初始化,收尾函数,以及一些函数指针,用来告诉Zend,如何获取,和输出数据。 编程

1. php_cgi_startup, 当一个应用要调用PHP的时候,这个函数会被调用,对于CGI来讲,它只是简单的调用了PHP的初始化函数:

1 staticintphp_cgi_startup(sapi_module_struct *sapi_module)
2 {
3     if(php_module_startup(sapi_module, NULL, 0) == FAILURE) {
4         returnFAILURE;
5     }
6     returnSUCCESS;
7 }

2. php_module_shutdown_wrapper , 一个对PHP关闭函数的简单包装。只是简单的调用php_module_shutdown;

3. PHP会在每一个request的时候,处理一些初始化,资源分配的事务。这部分就是activate字段要定义的,从上面的结构咱们能够看出,对于CGI 来讲,它并无提供初始化处理句柄。对于mod_php来讲,那就不一样了,他要在apache的pool中注册资源析构函数, 申请空间, 初始化环境变量,等等。

4. sapi_cgi_deactivate, 这个是对应与activate的函数,顾名思义,它会提供一个handler, 用来处理收尾工做,对于CGI来讲,他只是简单的刷新缓冲区,用以保证用户在Zend关闭前获得全部的输出数据:

01 staticintsapi_cgi_deactivate(TSRMLS_D)
02 {
03     /* flush only when SAPI was started. The reasons are:
04         1. SAPI Deactivate is called from two places: module init and request shutdown
05         2. When the first call occurs and the request is not set up, flush fails on
06             FastCGI.
07     */
08     if(SG(sapi_started)) {
09         sapi_cgibin_flush(SG(server_context));
10     }
11     returnSUCCESS;
12 }

5. sapi_cgibin_ub_write, 这个hanlder告诉了Zend,如何输出数据,对于mod_php来讲,这个函数提供了一个向response数据写的接口,而对于CGI来讲,只是简单的写到stdout:

01 staticinlinesize_tsapi_cgibin_single_write(constchar*str, uint str_length TSRMLS_DC)
02 {
03 #ifdef PHP_WRITE_STDOUT
04     longret;
05 #else
06     size_tret;
07 #endif
08   
09 #if PHP_FASTCGI
10     if(fcgi_is_fastcgi()) {
11         fcgi_request *request = (fcgi_request*) SG(server_context);
12         longret = fcgi_write(request, FCGI_STDOUT, str, str_length);
13         if(ret <= 0) {
14             return0;
15         }
16         returnret;
17     }
18 #endif
19 #ifdef PHP_WRITE_STDOUT
20     ret = write(STDOUT_FILENO, str, str_length);
21     if(ret <= 0)return0;
22     returnret;
23 #else
24     ret =fwrite(str, 1, MIN(str_length, 16384), stdout);
25     returnret;
26 #endif
27 }
28   
29 staticintsapi_cgibin_ub_write(constchar*str, uint str_length TSRMLS_DC)
30 {
31     constchar*ptr = str;
32     uint remaining = str_length;
33     size_tret;
34   
35     while(remaining > 0) {
36         ret = sapi_cgibin_single_write(ptr, remaining TSRMLS_CC);
37         if(!ret) {
38             php_handle_aborted_connection();
39             returnstr_length - remaining;
40         }
41         ptr += ret;
42         remaining -= ret;
43     }
44   
45     returnstr_length;
46 }

把真正的写的逻辑剥离出来,就是为了简单实现兼容fastcgi的写方式。

6. sapi_cgibin_flush, 这个是提供给zend的刷新缓存的函数句柄,对于CGI来讲,只是简单的调用系统提供的fflush;

7.NULL, 这部分用来让Zend能够验证一个要执行脚本文件的state,从而判断文件是否据有执行权限等等,CGI没有提供。

8. sapi_cgibin_getenv, 为Zend提供了一个根据name来查找环境变量的接口,对于mod_php5来讲,当咱们在脚本中调用getenv的时候,就会间接的调用这个句柄。而 对于CGI来讲,由于他的运行机制和CLI很相似,直接调用父级是Shell, 因此,只是简单的调用了系统提供的genenv:

01 staticchar*sapi_cgibin_getenv(char*name,size_tname_len TSRMLS_DC)
02 {
03 #if PHP_FASTCGI
04     /* when php is started by mod_fastcgi, no regular environment
05        is provided to PHP.  It is always sent to PHP at the start
06        of a request.  So we have to do our own lookup to get env
07        vars.  This could probably be faster somehow.  */
08     if(fcgi_is_fastcgi()) {
09         fcgi_request *request = (fcgi_request*) SG(server_context);
10         returnfcgi_getenv(request, name, name_len);
11     }
12 #endif
13     /*  if cgi, or fastcgi and not found in fcgi env
14         check the regular environment */
15     returngetenv(name);
16 }

9. php_error, 错误处理函数, 到这里,说几句题外话,上次看到php maillist 提到的使得PHP的错误处理机制彻底OO化, 也就是,改写这个函数句柄,使得每当有错误发生的时候,都throw一个异常。而CGI只是简单的调用了PHP提供的错误处理函数。

10. 这个函数会在咱们调用PHP的header()函数的时候被调用,对于CGI来讲,不提供。

11. sapi_cgi_send_headers, 这个函数会在要真正发送header的时候被调用,通常来讲,就是当有任何的输出要发送以前:

01 staticintsapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
02 {
03     charbuf[SAPI_CGI_MAX_HEADER_LENGTH];
04     sapi_header_struct *h;
05     zend_llist_position pos;
06   
07     if(SG(request_info).no_headers == 1) {
08         return SAPI_HEADER_SENT_SUCCESSFULLY;
09     }
10   
11     if(cgi_nph || SG(sapi_headers).http_response_code != 200)
12     {
13         intlen;
14   
15         if(rfc2616_headers && SG(sapi_headers).http_status_line) {
16             len = snprintf(buf, SAPI_CGI_MAX_HEADER_LENGTH,
17                            "%s\r\n", SG(sapi_headers).http_status_line);
18   
19             if(len > SAPI_CGI_MAX_HEADER_LENGTH) {
20                 len = SAPI_CGI_MAX_HEADER_LENGTH;
21             }
22   
23         }else{
24             len =sprintf(buf,"Status: %d\r\n", SG(sapi_headers).http_response_code);
25         }
26   
27         PHPWRITE_H(buf, len);
28     }
29   
30     h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);
31     while(h) {
32         /* prevent CRLFCRLF */
33         if(h->header_len) {
34             PHPWRITE_H(h->header, h->header_len);
35             PHPWRITE_H("\r\n", 2);
36         }
37         h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
38     }
39     PHPWRITE_H("\r\n", 2);
40   
41     returnSAPI_HEADER_SENT_SUCCESSFULLY;
42    }

12. NULL, 这个用来单独发送每个header, CGI没有提供

13. sapi_cgi_read_post, 这个句柄指明了如何获取POST的数据,若是作过CGI编程的话,咱们就知道CGI是从stdin中读取POST DATA的:

01 staticintsapi_cgi_read_post(char*buffer, uint count_bytes TSRMLS_DC)
02 {
03     uint read_bytes=0, tmp_read_bytes;
04 #if PHP_FASTCGI
05     char*pos = buffer;
06 #endif
07   
08     count_bytes = MIN(count_bytes, (uint) SG(request_info).content_length - SG(read_post_bytes));
09     while(read_bytes < count_bytes) {
10 #if PHP_FASTCGI
11         if(fcgi_is_fastcgi()) {
12             fcgi_request *request = (fcgi_request*) SG(server_context);
13             tmp_read_bytes = fcgi_read(request, pos, count_bytes - read_bytes);
14             pos += tmp_read_bytes;
15         }else{
16             tmp_read_bytes = read(0, buffer + read_bytes, count_bytes - read_bytes);
17         }
18 #else
19         tmp_read_bytes = read(0, buffer + read_bytes, count_bytes - read_bytes);
20 #endif
21   
22         if(tmp_read_bytes <= 0) {
23             break;
24         }
25         read_bytes += tmp_read_bytes;
26     }
27     returnread_bytes;
28 }

14. sapi_cgi_read_cookies, 这个和上面的函数同样,只不过是去获取cookie值:

1 staticchar*sapi_cgi_read_cookies(TSRMLS_D)
2 {
3     returnsapi_cgibin_getenv((char*)"HTTP_COOKIE",sizeof("HTTP_COOKIE")-1 TSRMLS_CC);
4 }

15. sapi_cgi_register_variables, 这个函数给了一个接口,用以给$_SERVER变量中添加变量,对于CGI来讲,注册了一个PHP_SELF,这样咱们就能够在脚本中访 问$_SERVER['PHP_SELF']来获取本次的request_uri:

1 staticvoidsapi_cgi_register_variables(zval *track_vars_array TSRMLS_DC)
2 {
3     /* In CGI mode, we consider the environment to be a part of the server
4      * variables
5      */
6     php_import_environment_variables(track_vars_array TSRMLS_CC);
7     /* Build the special-case PHP_SELF variable for the CGI version */
8     php_register_variable("PHP_SELF", (SG(request_info).request_uri ? SG(request_info).request_uri :""), track_vars_array TSRMLS_CC);
9 }

16. sapi_cgi_log_message ,用来输出错误信息,对于CGI来讲,只是简单的输出到stderr:

01 staticvoidsapi_cgi_log_message(char*message)
02 {
03 #if PHP_FASTCGI
04     if(fcgi_is_fastcgi() && fcgi_logging) {
05         fcgi_request *request;
06         TSRMLS_FETCH();
07   
08         request = (fcgi_request*) SG(server_context);
09         if(request) {
10             intlen =strlen(message);
11             char*buf =malloc(len+2);
12   
13             memcpy(buf, message, len);
14             memcpy(buf + len,"\n",sizeof("\n"));
15             fcgi_write(request, FCGI_STDERR, buf, len+1);
16             free(buf);
17         }else{
18             fprintf(stderr,"%s\n", message);
19         }
20         /* ignore return code */
21     }else
22 #endif /* PHP_FASTCGI */
23     fprintf(stderr,"%s\n", message);
24 }

通过分析,咱们已经了解了一个SAPI是如何实现的了, 分析过CGI之后,咱们也就能够想象mod_php, embed等SAPI的实现机制。

一次请求的开始与结束


PHP开始执行之后会通过两个主要的阶段:

  • 处理请求以前的开始阶段
  • 请求以后的结束阶段

开始阶段有两个过程:

第一个过程是模块初始化阶段(MINIT), 在整个SAPI生命周期内(例如Apache启动之后的整个生命周期内或者命令行程序整个执行过程当中), 该过程只进行一次。

第二个过程是模块激活阶段(RINIT),该过程发生在请求阶段, 例如经过url请求某个页面,则在每次请求以前都会进行模块激活(RINIT请求开始)。 例如PHP注册了一些扩展模块,则在MINIT阶段会回调全部模块的MINIT函数。 模块在这个阶段能够进行一些初始化工做,例如注册常量,定义模块使用的类等等。

模块在实现时能够经过以下宏来实现这些回调函数:

1 PHP_MINIT_FUNCTION(myphpextension)
2 {
3     // 注册常量或者类等初始化操做
4     returnSUCCESS;
5 }

请求到达以后PHP初始化执行脚本的基本环境,例如建立一个执行环境,包括保存PHP运行过程当中变量名称和值内容的符号表, 以及当前全部的函数以及类等信息的符号表。而后PHP会调用全部模块的RINIT函数, 在这个阶段各个模块也能够执行一些相关的操做,模块的RINIT函数和MINIT回调函数相似:

1 PHP_RINIT_FUNCTION(myphpextension)
2 {
3     // 例如记录请求开始时间
4     // 随后在请求结束的时候记录结束时间。这样咱们就可以记录下处理请求所花费的时间了
5     returnSUCCESS;
6 }

请求处理完后就进入告终束阶段,通常脚本执行到末尾或者经过调用exit()或die()函数, PHP都将进入结束阶段。和开始阶段对应,结束阶段也分为两个环节,一个在请求结束后停用模块(RSHUWDOWN,对应RINIT), 一个在SAPI生命周期结束(Web服务器退出或者命令行脚本执行完毕退出)时关闭模块(MSHUTDOWN,对应MINIT)。

1 PHP_RSHUTDOWN_FUNCTION(myphpextension)
2 {
3     // 例如记录请求结束时间,并把相应的信息写入到日至文件中。
4     returnSUCCESS;
5 }


一次请求生命周期


咱们从未手动开启过PHP的相关进程,它是随着Apache的启动而运行的。PHP经过mod_php5.so模块和Apache相连(具体说来是SAPI,即服务器应用程序编程接口)。

PHP总共有三个模块:内核、Zend引擎、以及扩展层。

  • PHP内核用来处理请求、文件流、错误处理等相关操做;
  • Zend引擎(ZE)用以将源文件转换成机器语言,而后在虚拟机上运行它;
  • 扩展层是一组函数、类库和流,PHP使用它们来执行一些特定的操做。

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

深刻探讨

真正的内部运行过程没有这么简单。以上过程只是个简略版,让咱们再深刻挖掘一下,看看幕后还发生了些什么。

Apache启动后,PHP解释程序也随之启动。PHP的启动过程有两步:

  • 第一步是初始化一些环境变量,这将在整个SAPI生命周期中发生做用;
  • 第二步是生成只针对当前请求的一些变量设置。

PHP启动第一步

不清楚什么第一第二步是什么?别担忧,咱们接下来详细讨论一下。让咱们先看看第一步,也是最主要的一步。要记住的是,第一步的操做在任何请求到达以前就发生了。

启动Apache后,PHP解释程序也随之启动。PHP调用各个扩展的MINIT方法,从而使这些扩展切换到可用状态。看看php.ini文件里打开了哪些扩展吧。 MINIT的意思是“模块初始化”。各个模块都定义了一组函数、类库等用以处理其余请求。

一个典型的MINIT方法以下:

1 PHP_MINIT_FUNCTION(extension_name){/* Initialize functions, classes etc */}

PHP启动第二步

当一个页面请求发生时,SAPI层将控制权交给PHP层。因而PHP设置了用于回复本次请求所需的环境变量。同时,它还创建一个变量表,用来存放执 行过程 中产生的变量名和值。PHP调用各个模块的RINIT方法,即“请求初始化”。一个经典的例子是Session模块的RINIT,若是在php.ini中 启用了Session模块,那在调用该模块的RINIT时就会初始化$_SESSION变量,并将相关内容读入;RINIT方法能够看做是一个准备过程, 在程序执行之间就会自动启动。 一个典型的RINIT方法以下:

1 PHP_RINIT_FUNCTION(extension_name) {/* Initialize session variables,pre-populate variables, redefine global variables etc */}

PHP关闭第一步

如同PHP启动同样,PHP的关闭也分两步。一旦页面执行完毕(不管是执行到了文件末尾仍是用exit或die函数停止),PHP就会启动清理程 序。它会按顺序调用各个模块的RSHUTDOWN方法。 RSHUTDOWN用以清除程序运行时产生的符号表,也就是对每一个变量调用unset函数。

一个典型的RSHUTDOWN方法以下:

1 PHP_RSHUTDOWN_FUNCTION(extension_name) {/* Do memory management, unset all variables used in the last PHP call etc */}

PHP关闭第二步

最后,全部的请求都已处理完毕,SAPI也准备关闭了,PHP开始执行第二步:PHP调用每一个扩展的MSHUTDOWN方法,这是各个模块最后一次释放内存的机会。

一个典型的RSHUTDOWN方法以下:

1 PHP_MSHUTDOWN_FUNCTION(extension_name) {/* Free handlers and persistent memory etc */}

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


单进程SAPI生命周期

CLI/CGI模式的PHP属于单进程的SAPI模式。这类的请求在处理一次请求后就关闭。也就是只会通过以下几个环节: 开始 - 请求开始 - 请求关闭 - 结束 SAPI接口实现就完成了其生命周期。

单进程多请求则以下图所示:

多进程的SAPI生命周期

一般PHP是编译为apache的一个模块来处理PHP请求。Apache通常会采用多进程模式, Apache启动后会fork出多个子进程,每一个进程的内存空间独立,每一个子进程都会通过开始和结束环节, 不过每一个进程的开始阶段只在进程fork出来以来后进行,在整个进程的生命周期内可能会处理多个请求。 只有在Apache关闭或者进程被结束以后才会进行关闭阶段,在这两个阶段之间会随着每一个请求重复请求开始-请求关闭的环节。

多进程SAPI生命周期

多线程的SAPI生命周期

多线程模式和多进程中的某个进程相似,不一样的是在整个进程的生命周期内会并行的重复着 请求开始-请求关闭的环节。

多线程SAPI生命周期



Zend引擎


相信不少人都据说过 Zend Engine 这个名词,也有不少人知道 Zend Engine 就是 PHP 语言的核心,但若要问一句:Zend Engine 到底存在于何处?或者说,Zend Engine 到底是在何时怎么发挥做用让 PHP 源码输出咱们想要的东西的?

Zend引擎是PHP实现的核心,提供了语言实现上的基础设施。例如:PHP的语法实现,脚本的编译运行环境, 扩展机制以及内存管理等,固然这里的PHP指的是官方的PHP实现(除了官方的实现, 目前比较知名的有facebook的hiphop实现,不过到目前为止,PHP尚未一个标准的语言规范),而PHP则提供了请求处理和其余Web服务器 的接口(SAPI)。

要理解 Zend Engine 的做用,就不能不理解为何会出现,PHP 为何须要 Zend Engine, Zend Engine 的出现为 PHP 解决了什么问题。PHP 发展到 3.0 版本的时候,此时 PHP 已经很普及了。“在 PHP3 的顶峰,Internet 上 10% 的 web 服务器上都安装了它”,PHP Manual 如是说。普遍的应用必然带来更高的要求。但此时的 PHP3 却有些力不从心了,这主要是由于 PHP3 采用的是边解释边执行的运行方式,运行效率很受其影响。其次,代码总体耦合度比较高,可扩展性也不够好,不利于应付各类各样需求。所以,此时在 PHP 界里已经有点中流砥柱做用的 Zeev Suraski 和 Andi Gutmans 决定重写代码以解决这两个问题。最终他们俩把该项技术的核心引擎命名为 Zend Engine,Zend 的意思即为 Zeev + Andi 。

Zend Engine 最主要的特性就是把 PHP 的边解释边执行的运行方式改成先进行预编译(Compile),而后再执行(Execute)。这二者的分开给 PHP 带来了革命性的变化:执行效率大幅提升;因为实行了功能分离,下降了模块间耦合度,可扩展性也大大加强。此时 PHP 已经能很方便的应付各类各样的 BT 需求了,而伴随 PHP 4.4.x ―多是 PHP4 系列的最后一个分支―的发布,PHP 的大部分开发人员已经将注意力放在了 PHP5 或者 PHP6 上面,之后发布的基本上就是一些 Bug Fix Release。能够说第一代的 Zend Engine 是已经在站最后一班岗了。

2004 年 7 月,PHP 5 发布,支持 PHP5 的是 Zend Engine 2.0 版本。这个版本主要是对 PHP 的 OO 功能进行了改进(我没有提集成 SQLite、PDO 等特性是由于咱们如今谈的主要是 Zend Engine 而非 PHP)。核心执行方式(非 OO 部分)较PHP4 的1.0 版本变更不大,因此 PHP5 纯粹的执行速度相对于 PHP4 没有大的提升。而预计将于本月中旬发布的 PHP 5.1 版本则会携带 Zend Engine 2.1 版本,这个版本将提供新的执行方式,执行速度也会快上许多,至少要比 PHP5.0 相对于 PHP4.x 的差异要大不少,因此,PHP 5.1 将会是一个很了很使人期待的版本。

但并不是 PHP5 系列的 Zend Engine 2 就天衣无缝了。前面已经提到过,Zend Engine 将代码分红编译和执行两大部分。通常状况下,咱们的代码完成之后就不多再去改变了。但执行时 PHP 却不得不还得一次又一次的重复编译,这根本就是毫无必要的。并且一般状况下,编译的所花费的时间并不比执行少多少,说是五五开并不为过,所以这极大的浪费 了机器的 CPU。基于 Zend Engine 3.0 的 PHP6 将试图解决这个问题。除此以外,目前的 PHP 对多字节的字符处理也是 PHP 的一大体命缺陷。这在人们联系日益国际化的今天几乎是不可忍受的。而无数人在抨击 PHP 或 比较 ASP 等同类语言时老是不可避免的要提到这一点。同时受到 IBM 方面的压力,PHP6 也将会把对多字节字符的处理提到首要日程。这在 PHP6 的 Dev 版本中已经获得体现。

目前PHP的实现和Zend引擎之间的关系很是紧密,甚至有些过于紧密了,例如不少PHP扩展都是使用的Zend API, 而Zend正是PHP语言自己的实现,PHP只是使用Zend这个内核来构建PHP语言的,而PHP扩展大都使用Zend API, 这就致使PHP的不少扩展和Zend引擎耦合在一块儿了,后来才有PHP核心开发者就提出将这种耦合解开的建议。

目前PHP的受欢迎程度是毋庸置疑的,但凡流行的语言一般都会出现这个语言的其余实现版本, 这在Java社区里就很是明显,目前已经有很是多基于JVM的语言了,例如IBM的Project Zero就实现了一个基于JVM的PHP实现, .NET也有相似的实现,一般他们这样作的缘由无非是由于:他们喜欢这个语言,但又不想放弃原有的平台, 或者对现有的语言实现不满意,处于性能或者语言特性等(HipHop就是这样诞生的)。

不少脚本语言中都会有语言扩展机制,PHP中的扩展一般是经过Pear库或者原生扩展,在Ruby中则这二者的界限不是很明显, 他们甚至会提供两套实现,一个主要用于在没法编译的环境下使用,而在合适的环境则使用C实现的原生扩展, 这样在效率和可移植性上均可以保证。目前这些为PHP编写的扩展一般都没法在其余的PHP实现中实现重用, HipHop的作法是对最为流行的扩展进行重写。若是PHP扩展能和ZendAPI解耦,则在其余语言中重用这些扩展也将更加容易了。

在PHP的生命周期的各个阶段,一些与服务相关的操做都是经过SAPI接口实现。 这些内置实现的物理位置在PHP源码的SAPI目录。这个目录存放了PHP对各个服务器抽象层的代码, 例如命令行程序的实现,Apache的mod_php模块实现以及fastcgi的实现等等。

在各个服务器抽象层之间遵照着相同的约定,这里咱们称之为SAPI接口。 每一个SAPI实现都是一个_sapi_module_struct结构体变量。(SAPI接口)。 在PHP的源码中,当须要调用服务器相关信息时,所有经过SAPI接口中对应方法调用实现, 而这对应的方法在各个服务器抽象层实现时都会有各自的实现。

下面是为SAPI的简单示意图:

以cgi模式和apache2服务器为例,它们的启动方法以下:

1 cgi_sapi_module.startup(&cgi_sapi_module)  //  cgi模式 cgi/cgi_main.c文件
2   
3 apache2_sapi_module.startup(&apache2_sapi_module);
4  //  apache2服务器  apache2handler/sapi_apache2.c文件

这里的cgi_sapi_module是sapi_module_struct结构体的静态变量。 它的startup方法指向php_cgi_startup函数指针。在这个结构体中除了startup函数指针,还有许多其它方法或字段。 其部分定义以下:

01 struct_sapi_module_struct {
02     char*name;        //  名字(标识用)
03     char*pretty_name; //  更好理解的名字(本身翻译的)
04   
05     int(*startup)(struct_sapi_module_struct *sapi_module);   //  启动函数
06     int(*shutdown)(struct_sapi_module_struct *sapi_module);  //  关闭方法
07   
08     int(*activate)(TSRMLS_D); // 激活
09     int(*deactivate)(TSRMLS_D);   //  停用
10   
11     int(*ub_write)(constchar*str, unsignedintstr_length TSRMLS_DC);
12      //  不缓存的写操做(unbuffered write)
13     void(*flush)(void*server_context);   //  flush
14     structstat *(*get_stat)(TSRMLS_D);    //  get uid
15     char*(*getenv)(char*name,size_tname_len TSRMLS_DC);//  getenv
16   
17     void(*sapi_error)(inttype,constchar*error_msg, ...);  /* error handler */
18   
19     int(*header_handler)(sapi_header_struct *sapi_header, sapi_header_op_enum op,
20         sapi_headers_struct *sapi_headers TSRMLS_DC);  /* header handler */
21   
22      /* send headers handler */
23     int(*send_headers)(sapi_headers_struct *sapi_headers TSRMLS_DC);
24   
25     void(*send_header)(sapi_header_struct *sapi_header,
26             void*server_context TSRMLS_DC);  /* send header handler */
27   
28     int(*read_post)(char*buffer, uint count_bytes TSRMLS_DC);/* read POST data */
29     char*(*read_cookies)(TSRMLS_D);   /* read Cookies */
30   
31     /* register server variables */
32     void(*register_server_variables)(zval *track_vars_array TSRMLS_DC);
33   
34     void(*log_message)(char*message);    /* Log message */
35     time_t(*get_request_time)(TSRMLS_D);  /* Request Time */
36     void(*terminate_process)(TSRMLS_D);   /* Child Terminate */
37   
38     char*php_ini_path_override;   //  覆盖的ini路径
39   
40     ...
41     ...
42 };

以上的这些结构在各服务器的接口实现中都有定义。如Apache2的定义:

1 staticsapi_module_struct apache2_sapi_module = {
2     "apache2handler",
3     "Apache 2.0 Handler",
4   
5     php_apache2_startup,               /* startup */
6     php_module_shutdown_wrapper,           /* shutdown */
7   
8     ...
9 }

目前PHP内置的不少SAPI实现都已再也不维护或者变的有些非主流了,PHP社区目前正在考虑将一些SAPI移出代码库。 社区对不少功能的考虑是除非真的很是必要,或者某些功能已近很是通用了,不然就在PECL库中, 例如很是流行的APC缓存扩展将进入核心代码库中。

整个SAPI相似于一个面向对象中的模板方法模式的应用。 SAPI.c和SAPI.h文件所包含的一些函数就是模板方法模式中的抽象模板, 各个服务器对于sapi_module的定义及相关实现则是一个个具体的模板。

这样的结构在PHP的源码中有多处使用, 好比在PHP扩展开发中,每一个扩展都须要定义一个zend_module_entry结构体。 这个结构体的做用与sapi_module_struct结构体相似,都是一个相似模板方法模式的应用。 在PHP的生命周期中若是须要调用某个扩展,其调用的方法都是zend_module_entry结构体中指定的方法, 如在上一小节中提到的在执行各个扩展的请求初始化时,都是统一调用request_startup_func方法, 而在每一个扩展的定义时,都经过宏PHP_RINIT指定request_startup_func对应的函数。 以VLD扩展为例:其请求初始化为PHP_RINIT(vld),与之对应在扩展中须要有这个函数的实现:

1 PHP_RINIT_FUNCTION(vld) {
2 }

因此, 咱们在写扩展时也须要实现扩展的这些接口,一样,当实现各服务器接口时也须要实现其对应的SAPI。

Apache模块介绍



Apache概述

Apache是目前世界上使用最为普遍的一种Web Server,它以跨平台、高效和稳定而闻名。按照去年官方统计的数据,Apache服务器的装机量占该市场60%以上的份额。尤为是在 X(Unix/Linux)平台上,Apache是最多见的选择。其它的Web Server产品,好比IIS,只能运行在Windows平台上,是基于微软.Net架构技术的不二选择。

Apache支持许多特性,大部分经过模块扩展实现。常见的模块包括mod_auth(权限验证)、mod_ssl(SSL和TLS支持) mod_rewrite(URL重写)等。一些通用的语言也支持以Apache模块的方式与Apache集成。 如Perl,Python,Tcl,和PHP等。

Apache并非没有缺点,它最为诟病的一点就是变得愈来愈重,被广泛认为是重量级的WebServer。因此,近年来又涌现出了不少轻量级的替 代产品,好比lighttpd,nginx等等,这些WebServer的优势是运行效率很高,但缺点也很明显,成熟度每每要低于Apache,一般只能 用于某些特定场合。

Apache组件逻辑图

Apache是基于模块化设计的,整体上看起来代码的可读性高于php的代码,它的核心代码并很少,大多数的功能都被分散到各个模块中,各个模块在 系统启动的时候按需载入。你若是想要阅读Apache的源代码,建议你直接从main.c文件读起,系统最主要的处理逻辑都包含在里面。

MPM(Multi -Processing Modules,多重处理模块)是Apache的核心组件之一,Apache经过MPM来使用操做系 统的资源,对进程和线程池进行管理。Apache为了可以得到最好的运行性能,针对不一样的平台(Unix/Linux、Window)作了优化,为不一样的 平台提供了不一样的MPM,用户能够根据实际状况进行选择,其中最常使用的MPM有prefork和worker两种。至于您的服务器正以哪一种方式运行,取 决于安装Apache过程当中指定的MPM编译参数,在X系统上默认的编译参数为prefork。因为大多数的Unix都不支持真正的线程,因此采用了预派 生子进程(prefork)方式,象Windows或者Solaris这些支持线程的平台,基于多进程多线程混合的worker模式是一种不错的选择。对 此感兴趣的同窗能够阅读有关资料,此处再也不多讲。Apache中还有一个重要的组件就是APR(Apache portable Runtime Library),即Apache可移植运行库,它是一个对操做系统调用的抽象库,用来实现Apache内部组件对操做系统的使用,提升系统的可移植性。 Apache对于php的解析,就是经过众多Module中的php Module来完成的。

Apache的逻辑构成以及与操做系统的关系

PHP与Apache

当PHP须要在Apache服务器下运行时,通常来讲,它能够mod_php5模块的形式集成, 此时mod_php5模块的做用是接收Apache传递过来的PHP文件请求,并处理这些请求, 而后将处理后的结果返回给Apache。若是咱们在Apache启动前在其配置文件中配置好了PHP模块(mod_php5), PHP模块经过注册apache2的ap_hook_post_config挂钩,在Apache启动的时候启动此模块以接受PHP文件的请求。

除了这种启动时的加载方式,Apache的模块能够在运行的时候动态装载, 这意味着对服务器能够进行功能扩展而不须要从新对源代码进行编译,甚至根本不须要中止服务器。 咱们所须要作的仅仅是给服务器发送信号HUP或者AP_SIG_GRACEFUL通知服务器从新载入模块。 可是在动态加载以前,咱们须要将模块编译成为动态连接库。此时的动态加载就是加载动态连接库。 Apache中对动态连接库的处理是经过模块mod_so来完成的,所以mod_so模块不能被动态加载, 它只能被静态编译进Apache的核心。这意味着它是随着Apache一块儿启动的。

Apache是如何加载模块的呢?咱们之前面提到的mod_php5模块为例。 首先咱们须要在Apache的配置文件httpd.conf中添加一行:

1 LoadModule php5_module modules/mod_php5.so

这里咱们使用了LoadModule命令,该命令的第一个参数是模块的名称,名称能够在模块实现的源码中找到。 第二个选项是该模块所处的路径。若是须要在服务器运行时加载模块, 能够经过发送信号HUP或者AP_SIG_GRACEFUL给服务器,一旦接受到该信号,Apache将从新装载模块, 而不须要从新启动服务器。

在配置文件中添加了所上所示的指令后,Apache在加载模块时会根据模块名查找模块并加载, 对于每个模块,Apache必须保证其文件名是以“mod_”开始的,如PHP的mod_php5.c。 若是命名格式不对,Apache将认为此模块不合法。Apache的每个模块都是以module结构体的形式存在, module结构的name属性在最后是经过宏STANDARD20_MODULE_STUFF以__FILE__体现。 关于这点能够在后面介绍mod_php5模块时有看到。这也就决定了咱们的文件名和模块名是相同的。 经过以前指令中指定的路径找到相关的动态连接库文件后,Apache经过内部的函数获取动态连接库中的内容, 并将模块的内容加载到内存中的指定变量中。

在真正激活模块以前,Apache会检查所加载的模块是否为真正的Apache模块, 这个检测是经过检查module结构体中的magic字段实现的。 而magic字段是经过宏STANDARD20_MODULE_STUFF体现,在这个宏中magic的值为MODULE_MAGIC_COOKIE, MODULE_MAGIC_COOKIE定义以下:

1 #define MODULE_MAGIC_COOKIE 0x41503232UL /* "AP22" */

最后Apache会调用相关函数(ap_add_loaded_module)将模块激活, 此处的激活就是将模块放入相应的链表中(ap_top_modules链表: ap_top_modules链表用来保存Apache中全部的被激活的模块,包括默认的激活模块和激活的第三方模块。)

经过mod_php5支持PHP



Apache对PHP的支持是经过Apache的模块mod_php5来支持的。若是但愿Apache支持PHP的话,在./configure步 骤须要指定--with-apxs2=/usr/local/apache2/bin/apxs 表示告诉编译器经过Apache的mod_php5 /apxs来提供对PHP5的解析。

在最后一步make install的时候咱们会看到将动态连接库libphp5.so(Apache模块)拷贝到apache2的安装目录的modules目录下,而且还需 要在httpd.conf配置文件中添加LoadModule语句来动态将libphp5.so 模块加载进来,从而实现Apache对php的支持。

因为该模式实在太经典了,所以这里关于安装部分不许备详述了,相对来讲比较简单。咱们知道nginx通常包括两个用途HTTP Server和Reverse Proxy Server(反向代理服务器)。在前端能够部署nginx做为reverse proxy server,后端布置多个Apache来实现机群系统server cluster架构的。

所以,实际生产中,咱们仍旧可以保留Apache+mod_php5的经典App Server,而仅仅使用nginx来当作前端的reverse proxy server来实现代理和负载均衡。 所以,建议nginx(1个或者多个)+多个apache的架构继续使用下去。

Apache2的mod_php5模块包括sapi/apache2handler和sapi/apache2filter两个目录 在apache2_handle/mod_php5.c文件中,模块定义的相关代码以下:

01 AP_MODULE_DECLARE_DATA module php5_module = {
02     STANDARD20_MODULE_STUFF,
03         /* 宏,包括版本,小版本,模块索引,模块名,下一个模块指针等信息,其中模块名以__FILE__体现 */
04     create_php_config,     /* create per-directory config structure */
05     merge_php_config,      /* merge per-directory config structures */
06     NULL,                  /* create per-server config structure */
07     NULL,                  /* merge per-server config structures */
08     php_dir_cmds,          /* 模块定义的全部的指令 */
09     php_ap2_register_hook
10         /* 注册钩子,此函数经过ap_hoo_开头的函数在一次请求处理过程当中对于指定的步骤注册钩子 */
11 };

它所对应的是Apache的module结构,module的结构定义以下:

01 typedefstructmodule_struct module;
02 structmodule_struct {
03     intversion;
04     intminor_version;
05     intmodule_index;
06     constchar*name;
07     void*dynamic_load_handle;
08     structmodule_struct *next;
09     unsignedlongmagic;
10     void(*rewrite_args) (process_rec *process);
11     void*(*create_dir_config) (apr_pool_t *p,char*dir);
12     void*(*merge_dir_config) (apr_pool_t *p,void*base_conf,void*new_conf);
13     void*(*create_server_config) (apr_pool_t *p, server_rec *s);
14     void*(*merge_server_config) (apr_pool_t *p,void*base_conf,void*new_conf);
15     constcommand_rec *cmds;
16     void(*register_hooks) (apr_pool_t *p);
17 }

上面的模块结构与咱们在mod_php5.c中所看到的结构有一点不一样,这是因为STANDARD20_MODULE_STUFF的缘由, 这个宏它包含了前面8个字段的定义。STANDARD20_MODULE_STUFF宏的定义以下:

1 /** Use this in all standard modules */
2 #define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, \
3                 MODULE_MAGIC_NUMBER_MINOR, \
4                 -1, \
5                 __FILE__, \
6                 NULL, \
7                 NULL, \
8                 MODULE_MAGIC_COOKIE, \
9                                 NULL     /* rewrite args spot */

在php5_module定义的结构中,php_dir_cmds是模块定义的全部的指令集合,其定义的内容以下:

01 constcommand_rec php_dir_cmds[] =
02 {
03     AP_INIT_TAKE2("php_value", php_apache_value_handler, NULL,
04         OR_OPTIONS,"PHP Value Modifier"),
05     AP_INIT_TAKE2("php_flag", php_apache_flag_handler, NULL,
06         OR_OPTIONS,"PHP Flag Modifier"),
07     AP_INIT_TAKE2("php_admin_value", php_apache_admin_value_handler,
08         NULL, ACCESS_CONF|RSRC_CONF,"PHP Value Modifier (Admin)"),
09     AP_INIT_TAKE2("php_admin_flag", php_apache_admin_flag_handler,
10         NULL, ACCESS_CONF|RSRC_CONF,"PHP Flag Modifier (Admin)"),
11     AP_INIT_TAKE1("PHPINIDir", php_apache_phpini_set, NULL,
12         RSRC_CONF,"Directory containing the php.ini file"),
13     {NULL}
14 };

这是mod_php5模块定义的指令表。它其实是一个command_rec结构的数组。 当Apache遇到指令的时候将逐一遍历各个模块中的指令表,查找是否有哪一个模块可以处理该指令, 若是找到,则调用相应的处理函数,若是全部指令表中的模块都不能处理该指令,那么将报错。 如上可见,mod_php5模块仅提供php_value等5个指令。

php_ap2_register_hook函数的定义以下:

1 voidphp_ap2_register_hook(apr_pool_t *p)
2 {
3     ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
4     ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE);
5     ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
6     ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
7 }

以上代码声明了pre_config,post_config,handler和child_init 4个挂钩以及对应的处理函数。 其中pre_config,post_config,child_init是启动挂钩,它们在服务器启动时调用。 handler挂钩是请求挂钩,它在服务器处理请求时调用。其中在post_config挂钩中启动php。 它经过php_apache_server_startup函数实现。php_apache_server_startup函数经过调用 sapi_startup启动sapi, 并经过调用php_apache2_startup来注册sapi module struct(此结构在本节开头中有说明), 最后调用php_module_startup来初始化PHP, 其中又会初始化ZEND引擎,以及填充zend_module_struct中 的treat_data成员(经过php_startup_sapi_content_types)等。

到这里,咱们知道了Apache加载mod_php5模块的整个过程,但是这个过程与咱们的SAPI有什么关系呢? mod_php5也定义了属于Apache的sapi_module_struct结构:

01 staticsapi_module_struct apache2_sapi_module = {
02 "apache2handler",
03 "Apache 2.0 Handler",
04   
05 php_apache2_startup,               /* startup */
06 php_module_shutdown_wrapper,           /* shutdown */
07   
08 NULL,                      /* activate */
09 NULL,                      /* deactivate */
10   
11 php_apache_sapi_ub_write,          /* unbuffered write */
12 php_apache_sapi_flush,             /* flush */
13 php_apache_sapi_get_stat,          /* get uid */
14 php_apache_sapi_getenv,            /* getenv */
15   
16 php_error,                 /* error handler */
17   
18 php_apache_sapi_header_handler,        /* header handler */
19 php_apache_sapi_send_headers,          /* send headers handler */
20 NULL,                      /* send header handler */
21   
22 php_apache_sapi_read_post,         /* read POST data */
23 php_apache_sapi_read_cookies,          /* read Cookies */
24   
25 php_apache_sapi_register_variables,
26 php_apache_sapi_log_message,           /* Log message */
27 php_apache_sapi_get_request_time,      /* Request Time */
28 NULL,                      /* Child Terminate */
29   
30 STANDARD_SAPI_MODULE_PROPERTIES
31 };

这些方法都专属于Apache服务器。以读取cookie为例,当咱们在Apache服务器环境下,在PHP中调用读取Cookie时, 最终获取的数据的位置是在激活SAPI时。它所调用的方法是read_cookies。

1 SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);

对于每个服务器在加载时,咱们都指定了sapi_module,而Apache的sapi_module是 apache2_sapi_module。 其中对应read_cookies方法的是php_apache_sapi_read_cookies函数。 这也是定义SAPI结构的理由:统一接口,面向接口的编程,具备更好的扩展性和适应性。

Apache运行与钩子函数



Apache是目前世界上使用最为普遍的一种Web Server,它以跨平台、高效和稳定而闻名。按照去年官方统计的数据,Apache服务器的装机量占该市场60%以上的份额。尤为是在 X(Unix/Linux)平台上,Apache是最多见的选择。其它的Web Server产品,好比IIS,只能运行在Windows平台上,是基于微软.Net架构技术的不二选择。

Apache并非没有缺点,它最为诟病的一点就是变得愈来愈重,被广泛认为是重量级的WebServer。因此,近年来又涌现出了不少轻量级的替 代产品,好比lighttpd,nginx等等,这些WebServer的优势是运行效率很高,但缺点也很明显,成熟度每每要低于Apache,一般只能 用于某些特定场合。

Apache的运行过程

Apache的运行分为启动阶段和运行阶段。 在启动阶段,Apache为了得到系统资源最大的使用权限,将以特权用户root(*nix系统)或超级管理员 Administrator(Windows系统)完成启动, 而且整个过程处于一个单进程单线程的环境中。 这个阶段包括配置文件解析(如http.conf文件)、模块加载(如mod_php,mod_perl)和系统资源初始化(例如日志文件、共享内存段、 数据库链接等)等工做。

Apache的启动阶段执行了大量的初始化操做,而且将许多比较慢或者花费比较高的操做都集中在这个阶段完成,以减小了后面处理请求服务的压力。

在运行阶段,Apache主要工做是处理用户的服务请求。 在这个阶段,Apache放弃特权用户级别,使用普通权限,这主要是基于安全性的考虑,防止因为代码的缺陷引发的安全漏洞。 Apache对HTTP的请求能够分为链接、处理和断开链接三个大的阶段。同时也能够分为11个小的阶段,依次为: Post-Read-Request,URI Translation,Header Parsing,Access Control,Authentication,Authorization, MIME Type Checking,FixUp,Response,Logging,CleanUp

Apache Hook机制

Apache的Hook机制是指:Apache 容许模块(包括内部模块和外部模块,例如mod_php5.so,mod_perl.so等)将自定义的函数注入到请求处理循环中。换句话说,模块能够在 Apache的任何一个处理阶段中挂接(Hook)上本身的处理函数,从而参与Apache的请求处理过程。

mod_php5.so/ php5apache2.dll就是将所包含的自定义函数,经过Hook机制注入到Apache中,在Apache处理流程的各个阶段负责处理php请求。

关于Hook机制在Windows系统开发也常常遇到,在Windows开发既有系统级的钩子,又有应用级的钩子。常见的翻译软件(例如金山词霸等等)的屏幕取词功能,大多数是经过安装系统级钩子函数完成的,将自定义函数替换gdi32.dll中的屏幕输出的绘制函数。

Apache 服务器的体系结构的最大特色,就是高度模块化。若是你为了追求处理效率,能够把这些dso模块在apache编译的时候静态链入,这样会提升Apache 5%左右的处理性能。

Apache请求处理循环

Apache请求处理循环的11个阶段都作了哪些事情呢?

  1. Post-Read-Request阶段。在正常请求处理流程中,这是模块能够插入钩子的第一个阶段。对于那些想很早进入处理请求的模块来讲,这个阶段能够被利用。
  2. URI Translation阶段。Apache在本阶段的主要工做:将请求的URL映射到本地文件系统。模块能够在这阶段插入钩子,执行本身的映射逻辑。mod_alias就是利用这个阶段工做的。
  3. Header Parsing阶段。Apache在本阶段的主要工做:检查请求的头部。因为模块能够在请求处理流程的任何一个点上执行检查请求头部的任务,所以这个钩子不多被使用。mod_setenvif就是利用这个阶段工做的。
  4. Access Control阶段。 Apache在本阶段的主要工做:根据配置文件检查是否容许访问请求的资源。Apache的标准逻辑实现了容许和拒绝指令。mod_authz_host就是利用这个阶段工做的。
  5. Authentication阶段。Apache在本阶段的主要工做:按照配置文件设定的策略对用户进行认证,并设定用户名区域。模块能够在这阶段插入钩子,实现一个认证方法。
  6. Authorization阶段。 Apache在本阶段的主要工做:根据配置文件检查是否容许认证过的用户执行请求的操做。模块能够在这阶段插入钩子,实现一个用户权限管理的方法。
  7. MIME Type Checking阶段。Apache在本阶段的主要工做:根据请求资源的MIME类型的相关规则,断定将要使用的内容处理函数。标准模块mod_negotiation和mod_mime实现了这个钩子。
  8. FixUp阶段。这是一个通用的阶段,容许模块在内容生成器以前,运行任何须要的处理流程。和Post_Read_Request相似,这是一个可以捕获任何信息的钩子,也是最常使用的钩子。
  9. Response阶段。Apache在本阶段的主要工做:生成返回客户端的内容,负责给客户端发送一个恰当的回复。这个阶段是整个处理流程的核心部分。
  10. Logging阶段。Apache在本阶段的主要工做:在回复已经发送给客户端以后记录事务。模块可能修改或者替换Apache的标准日志记录。 
  11. CleanUp阶段。 Apache在本阶段的主要工做:清理本次请求事务处理完成以后遗留的环境,好比文件、目录的处理或者Socket的关闭等等,这是Apache一次请求处理的最后一个阶段。

    从PHP源码目录结构的介绍以及PHP生命周期可知:嵌入式PHP相似CLI,也是SAPI接口的另外一种实现。 通常状况下,它的一个请求的生命周期也会和其它的SAPI同样:模块初始化=>请求初始化=>处理请求=>关闭请求=>关闭模 块。 固然,这只是理想状况。由于特定的应用由本身特殊的需求,只是在处理PHP脚本这个环节基本一致。

    对于嵌入式PHP或许咱们了解比较少,或者说根本用不到,甚至在网上相关的资料也很少, 例如不少游戏中使用Lua语言做为粘合语言,或者做为扩展游戏的脚本语言,相似的, 浏览器中的Javascript语言就是嵌入在浏览器中的。只是目前不多有应用将PHP做为嵌入语言来使用, PHP的强项目前仍是在Web开发方面。

    PHP对于嵌入式PHP的支持以及PHP为嵌入式提供了哪些接口或功能呢?首先咱们看下所要用到的示例源码:

    01 #include <sapi/embed/php_embed.h>
    02 #ifdef ZTS
    03     void***tsrm_ls;
    04 #endif
    05 /* Extension bits */
    06 zend_module_entry php_mymod_module_entry = {
    07     STANDARD_MODULE_HEADER,
    08     "mymod",/* extension name */
    09     NULL,/* function entries */
    10     NULL,/* MINIT */
    11     NULL,/* MSHUTDOWN */
    12     NULL,/* RINIT */
    13     NULL,/* RSHUTDOWN */
    14     NULL,/* MINFO */
    15     "1.0",/* version */
    16     STANDARD_MODULE_PROPERTIES
    17 };
    18 /* Embedded bits */
    19 staticvoidstartup_php(void)
    20 {
    21     intargc = 1;
    22     char*argv[2] = {"embed5", NULL };
    23     php_embed_init(argc, argv PTSRMLS_CC);
    24     zend_startup_module(&php_mymod_module_entry);
    25 }
    26 staticvoidexecute_php(char*filename)
    27 {
    28     zend_first_try {
    29         char*include_script;
    30         spprintf(&include_script, 0,"include '%s'", filename);
    31         zend_eval_string(include_script, NULL, filename TSRMLS_CC);
    32         efree(include_script);
    33     } zend_end_try();
    34 }
    35 intmain(intargc,char*argv[])
    36 {
    37     if(argc <= 1) {
    38         printf("Usage: embed4 scriptfile";);
    39         return-1;
    40     }
    41     startup_php();
    42     execute_php(argv[1]);
    43     php_embed_shutdown(TSRMLS_CC);
    44     return0;
    45 }

    以上的代码能够在《Extending and Embedding PHP》在第20章找到(原始代码有一个符号错误,有兴趣的童鞋能够去围观下)。 上面的代码是一个嵌入式PHP运行器(咱们权当其为运行器吧),在这个运行器上咱们能够运行PHP代码。 这段代码包括了对于PHP嵌入式支持的声明,启动嵌入式PHP运行环境,运行PHP代码,关闭嵌入式PHP运行环境。 下面咱们就这段代码分析PHP对于嵌入式的支持作了哪些工做。 首先看下第一行:

    1 #include <sapi/embed/php_embed.h>

    在sapi目录下的embed目录是PHP对于嵌入式的抽象层所在。在这里有咱们所要用到的函数或宏定义。 如示例中所使用的php_embed_init,php_embed_shutdown等函数。

    第2到4行:

    1 #ifdef ZTS
    2     void***tsrm_ls;
    3 #endif

    ZTS是Zend Thread Safety的简写,与这个相关的有一个TSRM(线程安全资源管理)的东东,这个后面的章节会有详细介绍,这里就再也不做阐述。

    第6到17行:

    01 zend_module_entry php_mymod_module_entry = {
    02     STANDARD_MODULE_HEADER,
    03     "mymod",/* extension name */
    04     NULL,/* function entries */
    05     NULL,/* MINIT */
    06     NULL,/* MSHUTDOWN */
    07     NULL,/* RINIT */
    08     NULL,/* RSHUTDOWN */
    09     NULL,/* MINFO */
    10     "1.0",/* version */
    11     STANDARD_MODULE_PROPERTIES
    12 };

    以上PHP内部的模块结构声明,此处对于模块初始化,请求初始化等函数指针均为NULL, 也就是模块在初始化及请求开始结束等事件发生的时候不执行任何操做。 不过这些操做在sapi/embed/php_embed.c文件中的php_embed_shutdown等函数中有体现。 关于模块结构的定义在zend/zend_modules.h中。

    startup_php函数:

    1 staticvoidstartup_php(void)
    2 {
    3     intargc = 1;
    4     char*argv[2] = {"embed5", NULL };
    5     php_embed_init(argc, argv PTSRMLS_CC);
    6     zend_startup_module(&php_mymod_module_entry);
    7 }

    这个函数调用了两个函数php_embed_init和zend_startup_module完成初始化工做。 php_embed_init函数定义在sapi/embed/php_embed.c文件中。它完成了PHP对于嵌入式的初始化支持。 zend_startup_module函数是PHP的内部API函数,它的做用是注册定义的模块,这里是注册mymod模块。 这个注册过程仅仅是将所定义的zend_module_entry结构添加到注册模块列表中。

    execute_php函数:

    1 staticvoidexecute_php(char*filename)
    2 {
    3     zend_first_try {
    4         char*include_script;
    5         spprintf(&include_script, 0,"include '%s'", filename);
    6         zend_eval_string(include_script, NULL, filename TSRMLS_CC);
    7         efree(include_script);
    8     } zend_end_try();
    9 }

    从函数的名称来看,这个函数的功能是执行PHP代码的。 它经过调用sprrintf函数构造一个include语句,而后再调用zend_eval_string函数执行这个include语句。 zend_eval_string最终是调用zend_eval_stringl函数,这个函数是流程是一个编译PHP代码, 生成zend_op_array类型数据,并执行opcode的过程。 这段程序至关于下面的这段php程序,这段程序能够用php命令来执行,虽然下面这段程序没有实际意义, 而经过嵌入式PHP中,你能够在一个用C实现的系统中嵌入PHP,而后用PHP来实现功能。

    1 <?php
    2 if($argc< 2)die("Usage: embed4 scriptfile");
    3   
    4 include$argv[1];
    5 ?>

    main函数:

    01 intmain(intargc,char*argv[])
    02 {
    03     if(argc <= 1) {
    04         printf("Usage: embed4 scriptfile";);
    05         return-1;
    06     }
    07     startup_php();
    08     execute_php(argv[1]);
    09     php_embed_shutdown(TSRMLS_CC);
    10     return0;
    11 }

    这个函数是主函数,执行初始化操做,根据输入的参数执行PHP的include语句,最后执行关闭操做,返回。 其中php_embed_shutdown函数定义在sapi/embed/php_embed.c文件中。它完成了PHP对于嵌入式的关闭操做支持。 包括请求关闭操做,模块关闭操做等。

    以上是使用PHP的嵌入式方式开发的一个简单的PHP代码运行器,它的这些调用的方式都基于PHP自己的一些实现, 而针对嵌入式的SAPI定义是很是简单的,没有Apache和CGI模式的复杂,或者说是至关简陋,这也是由其所在环境决定。 在嵌入式的环境下,不少的网络协议所须要的方法都再也不须要。以下所示,为嵌入式的模块定义。

    01 sapi_module_struct php_embed_module = {
    02     "embed",                      /* name */
    03     "PHP Embedded Library",       /* pretty name */
    04   
    05     php_embed_startup,             /* startup */
    06     php_module_shutdown_wrapper,  /* shutdown */
    07   
    08     NULL,                         /* activate */
    09     php_embed_deactivate,          /* deactivate */
    10   
    11     php_embed_ub_write,            /* unbuffered write */
    12     php_embed_flush,               /* flush */
    13     NULL,                         /* get uid */
    14     NULL,                         /* getenv */
    15   
    16     php_error,                    /* error handler */
    17   
    18     NULL,                         /* header handler */
    19     NULL,                         /* send headers handler */
    20     php_embed_send_header,         /* send header handler */
    21   
    22     NULL,                         /* read POST data */
    23     php_embed_read_cookies,        /* read Cookies */
    24   
    25     php_embed_register_variables,  /* register server variables */
    26     php_embed_log_message,         /* Log message */
    27     NULL,                          /* Get request time */
    28     NULL,                          /* Child terminate */
    29   
    30     STANDARD_SAPI_MODULE_PROPERTIES
    31 };
    32 /* }}} */

    在这个定义中咱们看到了若干的NULl定义,在前面一小节中说到SAPI时,咱们是以cookie的读取为例, 在这里也有读取cookie的实现——php_embed_read_cookies函数,可是这个函数的实现是一个空指针NULL。

PHP的FastCGI


CGI全称是“通用网关接口”(Common Gateway Interface), 它可让一个客户端,从网页浏览器向执行在Web服务器上的程序请求数据。 CGI描述了客户端和这个程序之间传输数据的一种标准。 CGI的一个目的是要独立于任何语言的,因此CGI能够用任何一种语言编写,只要这种语言具备标准输入、输出和环境变量。 如php,perl,tcl等。

FastCGI是Web服务器和处理程序之间通讯的一种协议, 是CGI的一种改进方案,FastCGI像是一个常驻(long-live)型的CGI, 它能够一直执行,在请求到达时不会花费时间去fork一个进程来处理(这是CGI最为人诟病的fork-and-execute模式)。 正是由于他只是一个通讯协议,它还支持分布式的运算,即 FastCGI 程序能够在网站服务器之外的主机上执行而且接受来自其它网站服务器来的请求。

FastCGI是语言无关的、可伸缩架构的CGI开放扩展,将CGI解释器进程保持在内存中,以此得到较高的性能。 CGI程序反复加载是CGI性能低下的主要缘由,若是CGI程序保持在内存中并接受FastCGI进程管理器调度, 则能够提供良好的性能、伸缩性、Fail-Over特性等。

通常状况下,FastCGI的整个工做流程是这样的:

  1. Web Server启动时载入FastCGI进程管理器(IIS ISAPI或Apache Module)
  2. FastCGI进程管理器自身初始化,启动多个CGI解释器进程(可见多个php-cgi)并等待来自Web Server的链接。
  3. 当客户端请求到达Web Server时,FastCGI进程管理器选择并链接到一个CGI解释器。 Web server将CGI环境变量和标准输入发送到FastCGI子进程php-cgi。
  4. FastCGI子进程完成处理后将标准输出和错误信息从同一链接返回Web Server。当FastCGI子进程关闭链接时, 请求便告处理完成。FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在Web Server中)的下一个链接。 在CGI模式中,php-cgi在此便退出了。

PHP的CGI实现了Fastcgi协议,是一个TCP或UDP协议的服务器接受来自Web服务器的请求, 当启动时建立TCP/UDP协议的服务器的socket监听,并接收相关请求进行处理。随后就进入了PHP的生命周期: 模块初始化,sapi初始化,处理PHP请求,模块关闭,sapi关闭等就构成了整个CGI的生命周期。

以TCP为例,在TCP的服务端,通常会执行这样几个操做步骤:

  1. 调用socket函数建立一个TCP用的流式套接字;
  2. 调用bind函数将服务器的本地地址与前面建立的套接字绑定;
  3. 调用listen函数将新建立的套接字做为监听,等待客户端发起的链接,当客户端有多个链接链接到这个套接字时,可能须要排队处理;
  4. 服务器进程调用accept函数进入阻塞状态,直到有客户进程调用connect函数而创建起一个链接;
  5. 当与客户端建立链接后,服务器调用read_stream函数读取客户的请求;
  6. 处理完数据后,服务器调用write函数向客户端发送应答。

PHP的FastCGI使你的全部php应用软件经过mod_fastci运行,而不是mod_phpsusexec。FastCGI应用速度很快 是由于他们持久稳定,没必要对每个请求都启动和初始化。这使得应用程序的开发成为可能,不然在CGI范例是不切实际的(例如一个大型的脚本,或者一个须要 链接单个或多个数据库的应用)。

FastCGI的优势:

  1. PHP脚本运行速度更快(3到30倍)。PHP解释程序被载入内存而不用每次须要时从存储器读取,极大的提高了依靠脚本运行的站点的性能。
  2. 须要使用更少的系统资源。因为服务器不用每次须要时都载入PHP解释程序,你能够将站点的传输速度提高很高而没必要增长cpu负担。
  3. 不须要对现有的代码做任何改变。现有的一切都适用于PHP的FastCGI。

可是也会有潜在问题:

  • 对全部的子目录(/home/USERNAME/public_html/php.ini)你只有一个可用的php.ini文件。这是优 化网站代码所必需的。若是你须要多个php.ini文件以适应不一样的脚本须要,你能够在任何子目录禁用PHP的快速CGI,而其他的地方则继续有效。若是 你须要这样作请联系support。
  • 你对PHP环境作的任何升级(如php.ini文件的改变)都有几分钟的延迟。这是由于为了更快的速度你的php.ini文件已经被载入内存,而不是每次须要时再从存储器从新读取。

    前面介绍了PHP的生命周期,PHP的SAPI,SAPI处于PHP整个架构较上层,而真正脚本的执行主要由Zend引擎来完成, 这一小节咱们介绍PHP脚本的执行。

    目前编程语言能够分为两大类:

    • 第一类是像C/C++, .NET, Java之类的编译型语言, 它们的共性是:运行以前必须对源代码进行编译,而后运行编译后的目标文件。
    • 第二类好比PHP, Javascript, Ruby, Python这些解释型语言, 他们都无需通过编译便可“运行”。

    虽然能够理解为直接运行,但它们并非真的直接就被能被机器理解, 机器只能理解机器语言,那这些语言是怎么被执行的呢, 通常这些语言都须要一个解释器, 由解释器来执行这些源码, 实际上这些语言仍是会通过编译环节,只不过它们通常会在运行的时候实时进行编译。为了效率,并非全部语言在每次执行的时候都会从新编译一遍, 好比PHP的各类opcode缓存扩展(如APC, xcache, eAccelerator等),好比Python会将编译的中间文件保存成pyc/pyo文件, 避免每次运行从新进行编译所带来的性能损失。

    PHP的脚本的执行也须要一个解释器, 好比命令行下的php程序,或者apache的mod_php模块等等。 前面提到了PHP的SAPI接口, 下面就以PHP命令行程序为例解释PHP脚本是怎么被执行的。 例如以下的这段PHP脚本:

    1 <?php
    2 $str="Hello, nowamagic!\n";
    3 echo$str;
    4 ?>

    假设上面的代码保存在名为hello.php的文件中, 用PHP命令行程序执行这个脚本:

    1 $ php ./hello.php

    这段代码的输出显然是Hello, nowamagic!, 那么在执行脚本的时候PHP/Zend都作了些什么呢? 这些语句是怎么样让php输出这段话的呢? 下面将一步一步的进行介绍。

    程序的执行

    1. 如上例中, 传递给php程序须要执行的文件, php程序完成基本的准备工做后启动PHP及Zend引擎, 加载注册的扩展模块。
    2. 初始化完成后读取脚本文件,Zend引擎对脚本文件进行词法分析,语法分析。而后编译成opcode执行。 如过安装了apc之类的opcode缓存, 编译环节可能会被跳过而直接从缓存中读取opcode执行。

    PHP在读取到脚本文件后首先对代码进行词法分析,PHP的词法分析器是经过lex生成的, 词法规则文件在$PHP_SRC/Zend/zend_language_scanner.l, 这一阶段lex会会将源代码按照词法规则切分一个一个的标记(token)。PHP中提供了一个函数token_get_all(), 该函数接收一个字符串参数, 返回一个按照词法规则切分好的数组。 例如将上面的php代码做为参数传递给这个函数:

    1 <?php
    2 $code=<<<PHP_CODE
    3 <?php
    4 $str="Hello, nowamagic\n";
    5 echo$str;
    6 PHP_CODE;
    7   
    8 var_dump(token_get_all($code));
    9 ?>

    运行上面的脚本你将会看到一以下的输出:

    01 array (
    02   0 =>
    03   array (
    04     0 => 368,      // 脚本开始标记
    05     1 => '<?php    // 匹配到的字符串
    06 ',
    07     2 => 1,
    08   ),
    09   1 =>
    10   array (
    11     0 => 371,
    12     1 =>' ',
    13     2 => 2,
    14   ),
    15   2 =>'=',
    16   3 =>
    17   array (
    18     0 => 371,
    19     1 =>' ',
    20     2 => 2,
    21   ),
    22   4 =>
    23   array (
    24     0 => 315,
    25     1 => '"Hello, nowamagic
    26 "',
    27     2 => 2,
    28   ),
    29   5 =>';',
    30   6 =>
    31   array (
    32     0 => 371,
    33     1 => '
    34 ',
    35     2 => 3,
    36   ),
    37   7 =>
    38   array (
    39     0 => 316,
    40     1 =>'echo',
    41     2 => 4,
    42   ),
    43   8 =>
    44   array (
    45     0 => 371,
    46     1 =>' ',
    47     2 => 4,
    48   ),
    49   9 =>';',

    这也是Zend引擎词法分析作的事情,将代码切分为一个个的标记,而后使用语法分析器(PHP使用bison生成语法分析器, 规则见$PHP_SRC/Zend/zend_language_parser。y), bison根据规则进行相应的处理, 若是代码找不到匹配的规则,也就是语法错误时Zend引擎会中止,并输出错误信息。 好比缺乏括号,或者不符合语法规则的状况都会在这个环节检查。 在匹配到相应的语法规则后,Zend引擎还会进行编译, 将代码编译为opcode, 完成后,Zend引擎会执行这些opcode, 在执行opcode的过程当中还有可能会继续重复进行编译-执行, 例如执行eval,include/require等语句, 由于这些语句还会包含或者执行其余文件或者字符串中的脚本。

    例如上例中的echo语句会编译为一条ZEND_ECHO指令, 执行过程当中,该指令由C函数zend_print_variable(zval* z)执行,将传递进来的字符串打印出来。 为了方便理解, 本例中省去了一些细节,例如opcode指令和处理函数之间的映射关系等。 后面的章节将会详细介绍。

    若是想直接查看生成的Opcode,可使用php的vld扩展查看。扩展下载地址: http://pecl.php.net/package/vld。Win下须要本身编译生成dll文件。

    有关PHP脚本编译执行的细节,请阅读后面有关词法分析,语法分析及opcode编译相关内容。未完待续.......

相关文章
相关标签/搜索