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