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下執行一個腳本的生命週期。

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