2

1、PHP与Apache工作模式

在传统的LAMP架构中,PHP与Apache交互时,至少有两种方式『运行PHP』:

  • 使用CGI:Apache发送请求至php-cgi进程,php-cgi进程调用PHP解释器,然后由PHP解释器进程解释php脚本代码。
  • 使用mod_php作为Apache的一个模块:PHP解释器作为Apache的一个内置模块,即不存在外部的PHP进程,而是由Apache(中的mod_php模块)进程解释执行PHP脚本 - 这意味着PHP与Apache通信更方便快捷。

其中,『运行PHP』是指调用PHP解释器解释执行PHP脚本。

通过php的'php_sapi_name()’函数可知道,当前系统采用哪种工作模式。如当值为’apache2handler’时即表示:mod_php模式。

2、Apache模块加载原理

Apache的模块可以以静态方式编译到可执行程序中,也可以在Apache运行过程中动态加载(以动态链接库的方式)。这意味着:可以对Apache服务器程序进行扩展而无需重新源码编译它,甚至无需重启它。
所需要做的就是:向服务器发送HUP或者AP_SIG_GRACEFUL信号,通知服务器重新加载模块。
关于向Apache发送HUP信号:

'Sending the HUP or restart signal to the parent causes it to kill off its children like in TERM, but the parent doesn't exit. It re-reads its configuration files, and re-opens any log files. Then it spawns a new set of children and continues serving hits.

回到mod_php模块,Apache动态加载模块的过程:

  • 首先,在Apache配置文件http.conf中增加:LoadModule php7_module libexec/apache2/libphp7.so,表示运行过程中加载PHP模块的动态链接库文件:libphp7.so。
  • 然后,通过Apache的内部函数(以Hook的方式)获取动态链接库的内容,并将PHP模块的内容加载到内存中指定的变量中。

其中PHP7源码中,PHP模块(php7_module)的数据结构为:

AP_MODULE_DECLARE_DATA module php7_module = {
     STANDARD20_MODULE_STUFF, /*宏,包括了module结构的前8个字段:版本号、小版本号、模块索引、模块名、当前模块指针、下一个动态加载的模块指针、魔数、rewrite_args函数指针*/
     create_php_config,          /* create per-directory config structure */
     merge_php_config,          /* merge per-directory config structures */
     NULL,                         /* create per-server config structure */
     NULL,                         /* merge per-server config structures */
     php_dir_cmds,               /* command apr_table_t */
     php_ap2_register_hook     /* register hooks */
};

其中,php_ap2_register_hook是一系列的hook调用:

void php_ap2_register_hook(apr_pool_t *p)
{
     ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
     ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE);
     ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
#ifdef ZEND_SIGNALS
     ap_hook_child_init(zend_signal_init, NULL, NULL, APR_HOOK_MIDDLE);
#endif
     ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
}
  • pre_config、post_config、child_init是启动时执行的钩子,它们会在Apache服务器启动时调用。其中,在post_config钩子中启动PHP解释器模块(由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)等')。
  • handler是请求时执行的钩子,它会在Apache服务器处理请求时调用。

3、Apache Hooking机制:

在Apache2.4中如果需要处理请求时,你只需要创建一个钩子(Hook),挂于请求处理程序上。
一个钩子,本质上是一条信息:告诉服务器它要么服务用户发起的请求要么只是瞥一眼该请求。Apache所有的模块(包括mod_rewrite, mod_authn_*, mod_proxy等)均是将钩子挂于请求程序的各个部分来实现的 - are hooked into specific parts of the request process。

modules serve different purposes; Some are authentication/authorization handlers, others are file or script handlers while some third modules rewrite URIs or proxies content.

Apache服务器本身无需知道每个模块具体负责处理哪个部分以及处理什么,它只需要:在客户端请求达到的时候询问下哪个模块对这个请求『感兴趣』即可,而每个模块只需选择要还是不要,如果要那按照钩子定义的内容处理然后返回接口。

图片描述

图片来源于Apache官网

Apache允许外部模块可以将自定义的函数注入到自己的请求处理循环中,从而参与Apache的请求处理过程。
通过Hook机制,PHP模块可以在Apache请求处理流程中负责处理那些关于php脚本的请求(即负责解释、执行php脚本)。
具体实现方式可以详见在PHP源码中实现Apahce的ap_hook_post_config钩子:PHP以模块方式注册到Apache的挂钩上去。这样在Apache进程运行时一有php请求,就可以加载动态链接库(libphp7.so文件)形式的PHP模块,用来处理php请求。

REFERENCES

1、http://stackoverflow.com/ques...
2、http://www.phppan.com/2011/01...
3、https://github.com/php/php-sr...
4、https://github.com/php/php-sr...
5、https://httpd.apache.org/docs...


yabohe
810 声望54 粉丝