以PHP7为学习基础,PHP7的源码为C编写的。
参考书籍:《PHP内核剖析》秦鹏/著
GitHub网页:https://github.com/pangudashu/php7-internal
目录
SAPI是PHP框架的接口层,是进入PHP内部的入口。PHP中实现的SAPI有很多,本次主要介绍三个典型的SAPI:Cli、Fpm、Embed。其中Cli、Fpm是完整实现的SAPI,他们有自己定义的main函数。
2.1 Cli
Cli,命令行接口,命令行下执行PHP脚本。4.3版本开始默认“./configure”时默认开启,可以用“--disable-cli”屏蔽。
Cli模式通过执行编译的PHP二进制文件即可启动,不同的参数做不同的处理,具体可参照PHP命令手册。直接在PHP命令后加PHP脚本则将执行该脚本。
//执行该脚本
$ php test.php
总的来说cli中可以按操作手册带不同的参数、也能带文件进行文件解析操作、也能直接操作PHP代码
2.1.1 执行流程
Cli是单进程模式,处理完请求之后就直接关闭了,生命周期先后经历module startup、request startup、execute script、request shutdown、module shutdown。关键处理流程是:
main()-> php_cli_startup() -> do_cli() -> php_module_shutdown()
1、Cli的main函数位于/sapi/cli/php_cli.c中,首先执行命令行参数解析,然后初始化sapi_module_struct。从源代码中可以发现有几个函数指针,它们是内核定义的操作接口的具体实现,用来告诉内核如何读取、输出数据。
static sapi_module_struct cli_sapi_module = {
"cli", /* name */
"Command Line Interface", /* pretty name */
php_cli_startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */
NULL, /* activate */
sapi_cli_deactivate, /* deactivate */
sapi_cli_ub_write, /* unbuffered write */
sapi_cli_flush, /* flush */
NULL, /* get uid */
NULL, /* getenv */
php_error, /* error handler */
sapi_cli_header_handler, /* header handler */
sapi_cli_send_headers, /* send headers handler */
sapi_cli_send_header, /* send header handler */
NULL, /* read POST data */
sapi_cli_read_cookies, /* read Cookies */
sapi_cli_register_variables, /* register server variables */
sapi_cli_log_message, /* Log message */
NULL, /* Get request time */
NULL, /* Child terminate */
STANDARD_SAPI_MODULE_PROPERTIES
};
2、完成参数解析及sapi_module_struct的基本初始化后,进入module startup阶段。startup函数为php_cli_startup(),这个函数直接调用了php_module_startup。
/* startup after we get the above ini override se we get things right */
if (sapi_module->startup(sapi_module) == FAILURE) {
/* there is no way to see if we must call zend_ini_deactivate()
* since we cannot check if EG(ini_directives) has been initialised
* because the executor's constructor does not set initialize it.
* Apart from that there seems no need for zend_ini_deactivate() yet.
* So we goto out_err.*/
exit_status = 1;
goto out;
}
3、module startup之后,进入请求初始化操作。
zend_first_try {
#ifndef PHP_CLI_WIN32_NO_CONSOLE
if (sapi_module == &cli_sapi_module) {
#endif
exit_status = do_cli(argc, argv);
#ifndef PHP_CLI_WIN32_NO_CONSOLE
} else {
exit_status = do_cli_server(argc, argv);
}
#endif
} zend_end_try();
4、do_cli()将完成请求的处理,此函数一开始对使用到的命令参数进行解析。接下来主要介绍PHP脚本请求时候的处理。
zend_file_handle file_handle;
...
if (script_file) {
//fopen请求的脚本文件
if (cli_seek_file_begin(&file_handle, script_file, &lineno) != SUCCESS) {
goto err;
} else {
char real_path[MAXPATHLEN];
if (VCWD_REALPATH(script_file, real_path)) {
translated_path = strdup(real_path);
}
script_filename = script_file;
}
}
...
//输入类型为ZEND_HANDLE_FP,也就是FILE*
file_handle.type = ZEND_HANDLE_FP;
因为PHP脚本执行时的输入形式有很多种,比如文件路径(filepath)、文件句柄(file)、文件描述符(fd)等,zend_file_handle结构就是用来定义不同输入形式的,这样可以统一PHP执行函数的输入参数。
typedef struct _zend_file_handle {
union {
int fd;
FILE *fp;
zend_stream stream; //zend封装的stream
} handle;
const char *filename; //文件路径
zend_string *opened_path;
//用于区分是哪种类型的:ZEND_HANDLE_FILENAME、ZEND_HANDLE_FD、ZEND_HANDLE_FP、ZEND_HANDLE_stream、ZEND_HANDLE_MAPPED
zend_stream_type type;
zend_bool free_filename;
} zend_file_handle;
Cli中使用的是文件句柄,在linux环境中调用fopen()打开一个文件。定义好请求的输入结构后将进行请求初始化操作了,即request startup阶段,然后开始PHP脚本的执行操作。
完成脚本处理后进入request shutdown阶段。
do_cli()完成后回到main()函数中,进入module shutdown阶段,最后进程退出,这就是Cli下执行一个脚本的生命周期。