Home >Backend Development >PHP Tutorial >In-depth understanding of PHP kernel (3) Overview-SAPI overview, in-depth understanding-sapi_PHP tutorial
Link to this article: http://www.orlion.ml/234/
1. At each stage of the PHP life cycle, some service-related operations are implemented through the SAPI interface. The physical location of these built-in implementations is in the SAPI directory of the PHP source code. This directory stores PHP's code for each server abstraction layer, such as the implementation of command line programs, Apache's mod_php module implementation, fastcgi implementation, etc.
The same convention is followed between each server abstraction layer, here we call it the SAPI interface. Each SAPI implementation is a _sapi_module_struct structure variable. (SAPI interface). In the PHP source code, when server-related information needs to be called, it is all implemented through corresponding method calls in the SAPI interface, and these methods will have their own implementations when each server abstraction layer is implemented. Due to the versatility of many operations, a large part of interface methods use default methods. The picture below is a simple schematic diagram of SPAI
Taking cgi mode and apache2 server as examples, their startup methods are as follows:
cgi_sapi_module.startup(&cgi_sapi_module) // cgi模式 cgi/cgi_main.c文件 <span> apache_sapi_module.startup(&apache_sapi_module); // apache服务器 apache2handler/sapi_apache2.c文件</span>
The cgi_sapi_module here is a static variable of the sapi_module_struct structure. Its startup method points to the php_cgi_startup function pointer. In addition to the startup function pointer, there are many other methods or fields in this structure. These structures are defined in the server's interface implementation
The entire SAPI is similar to an application of the template method pattern in object-oriented. Some functions included in the SAPI.c and SAPI.h files are abstract templates in the template method pattern. Each server's definition and related implementation of sapi_module is a specific template
2. Apache module
(1) When PHP needs to run under the Apache server, generally speaking, it can be integrated in the form of the mod_php5 module. At this time, the function of the mod_php5 module is to receive PHP file requests passed by Aapche and process these requests, and then Return the processed results to Apache. If we configure the PHP module in its configuration file before Apache starts, the PHP module registers the ap_hook_post_config hook of apache2 and starts this module when Apache starts to receive requests for PHP files.
In addition to this loading method at startup, Apache modules can be dynamically loaded at runtime, which means that the server can be expanded without the need to recompile the source code or even restart the server. All we need to do is to send the signal HUP or AP_SIG_GEACEFUL to the server to notify the server to reload the module. But before dynamic loading, we need to compile the module into a dynamic link library. Dynamic loading at this time is to load the dynamic link library. The processing of dynamic link libraries in Apache is completed through the module mod_so. Therefore, the mod_so module cannot be dynamically loaded. It can only be statically compiled into the core of Apache. This means it is started together with Apache.
How does Apache load modules? Taking mod_php5 as an example, first add a line in httpd.conf:
LoadModule php5_module modules/mod_php5.so
After adding the instructions shown in the configuration file, Apache will find the module based on the module name and load it when loading the module. Each module of Apache exists in the form of a module structure. The name attribute of the module structure is reflected in __FILE__ through the macro STANDARD20_MODULE_STUFF at the end. After finding the relevant dynamic link library file through the path specified in the previous instruction, Apache obtains the contents of the dynamic link library through internal functions and loads the contents of the module into the specified variable in memory.
Before actually activating the module, Apache will check whether all loaded modules are real Apache modules. Finally, Apache will call the relevant function (ap_add_loaded_module) to activate the module. The activation here is to put the module into the corresponding linked list (ap_top_modules linked list)
Apache loads the PHP module, so how is this module implemented? The mod_php5 module of Apache2 includes two directories: sapi/apache2handler and sapi/apache2filter. In the apache2_handle/mod_php5.c file, the relevant code for the module definition is as follows:
AP_MODULE_DECLARE_DATA module php5_module =<span> { STANDARD20_MODULE_STUFF, /* 宏,包括版本,小版本,模块索引,模块名,下一个模块指针等信息,其中模块名以__FILE__体现*/<span> create_php_config, /* create per-directory config structure */<span> merge_php_config, /* merge per-directory config structures */<span> NULL, /* create per-server config structure */<span> NULL, /* merge per-server config structures */<span> php_dir_cmds, /*模块定义的所有命令*/<span> php_ap2_register_hook /*注册钩子,此函数通过ap_hoo_开头的函数在一次处理过程中对于指定的步骤注册钩子*/<span> };</span></span></span></span></span></span></span></span>
It corresponds to the module structure of Apache. The module structure is defined as follows:
typedef struct<span> module_struct module; struct<span> module_struct { int<span> version; int<span> minor_version; int<span> module_index; const char *<span>name; void *<span>dynamic_load_handle; struct module_struct *<span>next; unsigned long<span> magic; void (*rewrite_args) (process_rec *<span>process); void *(*create_dir_config) (apr_pool_t *p, char *<span>dir); void *(*merge_dir_config) (apr_pool_t *p, void *base_conf, void *<span>new_conf); void *(*create_server_config) (apr_pool_t *p, server_rec *<span>s); void *(*merge_server_config) (apr_pool_t *p, void *base_conf, void *<span>new_conf); const command_rec *<span>cmds; void (*register_hooks) (apr_pool_t *<span>p); }</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
The above module structure is a little different from the structure we saw in mod_php5.c. This is due to STANDARD20_MODULE_STUFF, which contains the definition of the first 8 fields. The STANDARD20_MODULE_STUFF macro is defined as follows:
/** Use this in all standard modules */ #define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, \<span> MODULE_MAGIC_NUMBER_MINOR, \ -1<span>, \ __FILE__, \ NULL, \ NULL, \ MODULE_MAGIC_COOKIE, \ NULL /* rewrite args spot */</span></span>
In the structure defined by php5_module, php_dir_cmds is a set of all instructions defined by the module. The content of the definition is as follows:
const command_rec php_dir_cmds[] =<span> { AP_INIT_TAKE2("php_value"<span>, php_apache_value_handler, NULL, OR_OPTIONS, "PHP Value Modifier"<span>), AP_INIT_TAKE2("php_flag"<span>, php_apache_flag_handler, NULL, OR_OPTIONS, "PHP Flag Modifier"<span>), AP_INIT_TAKE2("php_admin_value"<span>, php_apache_admin_value_handler, NULL, ACCESS_CONF|RSRC_CONF, "PHP Value Modifier (Admin)"<span>), AP_INIT_TAKE2("php_admin_flag"<span>, php_apache_admin_flag_handler, NULL, ACCESS_CONF|RSRC_CONF, "PHP Flag Modifier (Admin)"<span>), AP_INIT_TAKE1("PHPINIDir"<span>, php_apache_phpini_set, NULL, RSRC_CONF, "Directory containing the php.ini file"<span>), {NULL} };</span></span></span></span></span></span></span></span></span></span></span>
This is the command list defined by the mod_php5 module. It is actually an array of commond_rec structures. When Apache encounters an instruction, it will go through the instruction tables in each module one by one to find out whether there is any module that can process the instruction. If found, the response processing function will be called. If all modules in the instruction table cannot process the instruction, Then an error will be reported. As seen above, the mod_php5 module only provides php_value and other 5 instructions.
The php_ap2_register_hook function is defined as follows:
void php_ap2_register_hook(apr_pool_t *<span>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); ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE); }</span>
以上代码声明了pre_config,post_config,handler和child_init4个挂钩以及对应的处理函数。其中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结构:
static sapi_module_struct apache2_sapi_module =<span> { "apache2handler"<span>, "Apache 2.0 Handler"<span>, php_apache2_startup, /* startup */<span> php_module_shutdown_wrapper, /* shutdown */<span> NULL, /* activate */<span> NULL, /* deactivate */<span> php_apache_sapi_ub_write, /* unbuffered write */<span> php_apache_sapi_flush, /* flush */<span> php_apache_sapi_get_stat, /* get uid */<span> php_apache_sapi_getenv, /* getenv */<span> php_error, /* error handler */<span> php_apache_sapi_header_handler, /* header handler */<span> php_apache_sapi_send_headers, /* send headers handler */<span> NULL, /* send header handler */<span> php_apache_sapi_read_post, /* read POST data */<span> php_apache_sapi_read_cookies, /* read Cookies */<span> php_apache_sapi_register_variables, php_apache_sapi_log_message, /* Log message */<span> php_apache_sapi_get_request_time, /* Request Time */<span> NULL, /* Child Terminate */<span> STANDARD_SAPI_MODULE_PROPERTIES };</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
这些方法都属于Apache服务器,以读取cookie为例,当我们在Apache服务器环境下,在PHP中调用读取Cookie时,最终获取的数据的位置是在激活SAPI时,它所调用的方法是read_cookie。
SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);
对于每一个服务器在加载时,我们都指定了sapi_module,而Apache的sapi_module是apache2_sapi_module。其中对应read_cookie的方法是php_apache_sapi_read_cookie函数。这也是定义SAPI结构的理由:统一接口,面向接口编程,具有更好的扩展性和适应性。
(2)Apache的运行过程
Apache的运行包括启动阶段和运行阶段,启动阶段Apache以root完成启动,整个过程处于单进程单线程的环境中,这个阶段包括配置文件解析、模块加载、系统资源初始化(例如日志文件、共享内存段、数据库连接等)等工作。
在运行阶段,Apache主要工作是处理用户的服务请求,在这个阶段Apache以普通用户运行。主要是安全性考虑,Apache对HTTP的请求可以分为连接、处理和断开连接三个大的阶段。
2、FastCGI
(1)cgi是通用网关接口(Common Gateway Intedface),它可以让一个客户端从网页浏览器向执行在Web服务器上的程序请求数据。CGI描述了客户端和这个程序之间传输数据的标准。CGI的一个目的是独立于任何语言,所以CGI可以用任何语言编写,只要这种语言具有标准输入、输出和环境变量。如PHP、perl、tcl等。
FastCGI是Web服务器和处理程序之间通信的一种协议,是CGI的一种改进方案,FastCGI像是一个常驻型的CGI,它可以一直执行,在请求到达时不会花费时间去fork一个进程来处理(这是CGI对位人诟病的fork-and-execute模式)。正是因为它只是一个通信协议,它还支持分布式的运算,即FastCGI程序可以在网站服务器以外的主机上执行并且接受来自其他网站服务器的请求
FastCGI的整个流程是这样的:
Step1:Web Server启动时载入FastCGI进程管理器(IIS ISAPI或Apache Module)
Step2:FastCGI进程管理器自身初始化,启动多个CGI解释器进程(可见多个php-cgi)并等待来自web server的连接
Step3:当客户端请求到达Web Server时,FastCGI进程管理器选择并连接到一个CGI解释器。Web Server将CGI环境变量和标准输入发送到FastCGI子进程php-cgi
Step4:FastCGI子进程完成处理后将标准输出和错误新词从同一连接返回Web Server 当FastCGI子进程关闭连接时,请求便结束。FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在Web Server中)的下一个连接。在CGI模式中,php-cgi在此便退出了。
(2)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函数向客户端发送应答
TCP上客户-服务器事务的时序如图所示:
php的CGI实现从cgi_main.c文件的main函数开始,在main函数中调用了定义在fastcgi.c文件中的初始化,监听等函数。对比TCP的流程,我们查看php对TCP协议的实现,虽然php本身也实现了这些流程,但是在main函数中一些过程被封装成一个函数实现。对应TCP的操作流程,PHP首先会执行创建socket,绑定套接字,创建监听:
if<span> (bindpath) { fcgi_fd = fcgi_listen(bindpath, 128); // socket˥˦2sfcgi_initɩ <span>Ȑ ... }</span></span>
在fastcgi.c文件中,fcig_listen函数主要用于创建、绑定socket并开始监听,它走完了前面所列TCP流程的前三个阶段,
if ((listen_socket = socket(sa.sa.sa_family, SOCK_STREAM, 0)) < 0 ||<span> ... bind(listen_socket, (struct sockaddr *) &sa, sock_len) < 0 ||<span> listen(listen_socket, backlog) < 0<span>) { ... }</span></span></span>
当服务端初始化完成后,进程调用accept函数进入阻塞状态,在main函数中我们看到如下代码:
while<span> (parent) { do<span> { pid = fork(); // oÒ <span>ȨėJ switch<span> (pid) { case 0: // ȨėJ parent = 0<span>; /* don't catch our signals */<span> sigaction(SIGTERM, &old_term, 0); // ľâ¯ķ sigaction(SIGQUIT, &old_quit, 0); // ľĿɰ£ƺ sigaction(SIGINT, &old_int, 0); // ľĿKȠƺ break<span>; ... default<span>: /* Fine */<span> running++<span>; break<span>; } while (parent && (running <<span> children)); ... while (!fastcgi || fcgi_accept_request(&request) >= 0<span>) { SG(server_context) = (void *) &<span>request; init_request_info(TSRMLS_C); CG(interactive) = 0<span>; ... }</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
如上的代码是一个生成子进程,并等待用户请求。在fcgi_accept_request函数中,程序会调用accept函数阻塞新创建的线程。当用户的请求到达时,fcgi_accept_request函数会判断是否处理用户的请求,其中会过滤某些连接请求,忽略受限制客户的请求,如果程序受理用户的请求,他将分析请求的信息,将相关的变量写到对应的变量中。其中在读取请求内容时调用了safe_read方法。如下所示:main()->fcgi_accept_request()->fcgi_read_request()->safe_read()
static inline ssize_t safe_read(fcgi_request *req, const void *<span>buf, size_t count) { size_t n = 0<span>; do<span> { ... // 省略 对win32的处理<span> ret = read(req->fd, ((char*)buf)+n, count-n); // 非win版本的读操作 <span>D ... // 省略 } while (n !=<span> count); }</span></span></span></span></span></span>
如上对应服务器端读取用户的请求数据。
在请求初始化完成,读取请求完毕后,就该处理请求的PHP文件了。假设此次请求为PHP_MODE_STANDARD则会调用php_execute_script执行PHP文件。在此函数中它先初始化此文件相关的一些内容,然后再调用zend_execute_scripts函数,对PHP文件进行词法分析和语法分析,生成中间代码,并执行zend_execute函数,从而执行这些中间代码。
在处理完用户的请求后,服务端将返回信息给客户端,此时在main函数中调用的是fcgi_finish_request(&request , 1);fcgi_finish_request函数定义在fasftcgi.c文件中。
在发送了请求的应答后,服务器端将会执行关闭操作,仅限于CGI本身的关闭,程序执行的是fcgi_close函数。