PHP内核剖析 SAPI之Cli

以PHP7为学习基础,PHP7的源码为C编写的。

参考书籍:《PHP内核剖析》秦鹏/著

GitHub网页:https://github.com/pangudashu/php7-internal

目录

2.1 Cli

2.1.1 执行流程


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下执行一个脚本的生命周期。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章