搜尋
首頁後端開發PHP8深入解析PHP8底層核心原始碼之SAPI(一)

這篇文章給大家深入解析PHP8底層核心源碼,了解一下SAPI。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。

深入解析PHP8底層核心原始碼之SAPI(一)

相關文章推薦:《解析PHP8底層核心原始碼-陣列(一)

在docker下搭建裡如下的環境

[root@a951700e857d cui-php]# php -v
PHP 8.0.2 (cli) (built: Mar  2 2021 02:40:03) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.2, Copyright (c) Zend Technologies
[root@a951700e857d cui-php]#

引入一張鳥哥的圖

深入理解Zend SAPIs(Zend SAPI Internals)

#https://link.zhihu.com/?target=https://www.laruence.com/2008/08/12/180.html

深入解析PHP8底層核心原始碼之SAPI(一)

SAPI(Server Application Programimg Interface,服務端應用程式介面)相當於PHP外部環境的代理程式。 PHP可以應用在終端機上,也可以應用在Web伺服器中,應用在終端機上的SAPI就叫作CLI SAPI,應用在Web伺服器中的就叫作CGI SAPI。

他相當於一個中間層或叫他膠水承上啟動作用

sapi的核心定義和巨集檔案在sapi.h中

深入解析PHP8底層核心原始碼之SAPI(一)

# cgi_main.c裡面有個重要的結構體

//* sapi_module_struct cgi_sapi_module 
static sapi_module_struct cgi_sapi_module = {
	"cgi-fcgi",						/* name */
	"CGI/FastCGI",					/* pretty name */

	php_cgi_startup,				/* startup */
	php_module_shutdown_wrapper,	/* shutdown */

	sapi_cgi_activate,				/* activate */
	sapi_cgi_deactivate,			/* deactivate */

	sapi_cgi_ub_write,				/* unbuffered write */
	sapi_cgi_flush,					/* flush */
	NULL,							/* get uid */
	sapi_cgi_getenv,				/* getenv */

	php_error,						/* error handler */

	NULL,							/* header handler */
	sapi_cgi_send_headers,			/* send headers handler */
	NULL,							/* send header handler */

	sapi_cgi_read_post,				/* read POST data */
	sapi_cgi_read_cookies,			/* read Cookies */

	sapi_cgi_register_variables,	/* register server variables */
	sapi_cgi_log_message,			/* Log message */
	NULL,							/* Get request time */
	NULL,							/* Child terminate */

	STANDARD_SAPI_MODULE_PROPERTIES
};

他」繼承「自結構體_sapi_module_struct

struct _sapi_module_struct {
	char *name;
	char *pretty_name;

	int (*startup)(struct _sapi_module_struct *sapi_module);
	int (*shutdown)(struct _sapi_module_struct *sapi_module);

	int (*activate)(void);
	int (*deactivate)(void);

	size_t (*ub_write)(const char *str, size_t str_length);
	void (*flush)(void *server_context);
	zend_stat_t *(*get_stat)(void);
	char *(*getenv)(const char *name, size_t name_len);

	void (*sapi_error)(int type, const char *error_msg, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);

	int (*header_handler)(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers);
	int (*send_headers)(sapi_headers_struct *sapi_headers);
	void (*send_header)(sapi_header_struct *sapi_header, void *server_context);

	size_t (*read_post)(char *buffer, size_t count_bytes);
	char *(*read_cookies)(void);

	void (*register_server_variables)(zval *track_vars_array);
	void (*log_message)(const char *message, int syslog_type_int);
	double (*get_request_time)(void);
	void (*terminate_process)(void);

	char *php_ini_path_override;

	void (*default_post_reader)(void);
	void (*treat_data)(int arg, char *str, zval *destArray);
	char *executable_location;

	int php_ini_ignore;
	int php_ini_ignore_cwd; /* don't look for php.ini in the current directory */

	int (*get_fd)(int *fd);

	int (*force_http_10)(void);

	int (*get_target_uid)(uid_t *);
	int (*get_target_gid)(gid_t *);

	unsigned int (*input_filter)(int arg, const char *var, char **val, size_t val_len, size_t *new_val_len);

	void (*ini_defaults)(HashTable *configuration_hash);
	int phpinfo_as_text;

	char *ini_entries;
	const zend_function_entry *additional_functions;
	unsigned int (*input_filter_init)(void);
};

註解後如下

#
struct _sapi_module_struct {
        char *name; // 名字,如cli、 fpm-fcgi等
        char *pretty_name; // 更易理解的名字,比如fpm-fcgi对应的为FPM/FastCGI
        int (*startup)(struct _sapi_module_struct *sapi_module);
        //模块启动时调用的函数
        int (*shutdown)(struct _sapi_module_struct *sapi_module);
        //模块结束时调用的函数
        int (*activate)(void); // 处理request时,激活需要调用的函数指针
        int (*deactivate)(void); // 处理完request时,使要调用的函数指针无效
        size_t (*ub_write)(const char *str, size_t str_length);
        // 这个函数指针用于输出数据
        void (*flush)(void *server_context); // 刷新缓存的函数指针
        zend_stat_t *(*get_stat)(void); // 判断对执行文件是否有执行权限
        char *(*getenv)(char *name, size_t name_len); // 获取环境变量的函数指针
        void (*sapi_error)(int type, const char *error_msg, ...)
            ZEND_ATTRIBUTE_FORMAT(printf, 2, 3); // 错误处理函数指针
        int (*header_handler)(sapi_header_struct *sapi_header,
            sapi_header_op_enum op, sapi_headers_struct *sapi_headers);
            //调用header()时被调用的函数指针
        int (*send_headers)(sapi_headers_struct *sapi_headers);
        // 发送全部header的函数指针
        void (*send_header)(sapi_header_struct *sapi_header, void *server_context);
        // 发送某一个header的函数指针
        size_t (*read_post)(char *buffer, size_t count_bytes);
        // 获取HTTP POST中数据的函数指针
        char *(*read_cookies)(void);  // 获取cookie中数据的函数指针
        void (*register_server_variables)(zval *track_vars_array);
        // 从$_SERVER中获取变量的函数指针
        void (*log_message)(char *message, int syslog_type_int);
        // 输出错误信息函数指针
        double (*get_request_time)(void); // 获取请求时间的函数指针
        void (*terminate_process)(void);  // 调用exit退出时的函数指针
        char *php_ini_path_override;  // PHP的ini文件被复写的地址

        void (*default_post_reader)(void); //负责解析POST数据的函数指针
        void (*treat_data)(int arg, char *str, zval *destArray);
        // 对数据进行处理的函数指针
        char *executable_location; // 执行的地理位置
        int php_ini_ignore; // 是否不使用任何ini配置文件
        int php_ini_ignore_cwd; // 忽略当前路径的php.ini
        int (*get_fd)(int *fd); // 获取执行文件的fd的函数指针
        int (*force_http_10)(void); // 强制使用http 1.0版本的函数指针
        int (*get_target_uid)(uid_t *); // 获取执行程序的uid的函数指针
        int (*get_target_gid)(gid_t *); // 获取执行程序的gid的函数指针
        unsigned int (*input_filter)(int arg, char *var, char **val, size_t val_len,
            size_t *new_val_len);
        // 对输入进行过滤的函数指针。比如将输入参数填充到自动全局变量$_GET、$_POST、$_COOKIE中
        void (*ini_defaults)(HashTable *configuration_hash);
        // 默认的ini配置的函数指针,把ini配置信息存在HashTable中
        int phpinfo_as_text; // 是否输出phpinfo信息

        char *ini_entries; // 执行时附带的ini配置,可以使用php -d设置
        const zend_function_entry *additional_functions;
        // 每个SAPI模块特有的一些函数注册,比如cli的cli_get_process_title
        unsigned int (*input_filter_init)(void);
};

這個結構體非常關鍵在CLI生命週期中,會呼叫其中定義的函數指標來實現各自的功能

另外還有一個重要的資料結構-sapi_globals,其對應的巨集為SG(v)

深入解析PHP8底層核心原始碼之SAPI(一)

typedef struct _sapi_globals_struct {
	void *server_context;
	sapi_request_info request_info;
	sapi_headers_struct sapi_headers;
	int64_t read_post_bytes;
	unsigned char post_read;
	unsigned char headers_sent;
	zend_stat_t global_stat;
	char *default_mimetype;
	char *default_charset;
	HashTable *rfc1867_uploaded_files;
	zend_long post_max_size;
	int options;
	zend_bool sapi_started;
	double global_request_time;
	HashTable known_post_content_types;
	zval callback_func;
	zend_fcall_info_cache fci_cache;
} sapi_globals_struct;

這個SG 共560個位元組 整個PHP生命週期都多次用到了它

回過頭看cgi_main.c  整個cgi 的程式碼一共有900多行

深入解析PHP8底層核心原始碼之SAPI(一)

折疊一些涉及ZTS (線程安全之類的)的程式碼

深入解析PHP8底層核心原始碼之SAPI(一)

zend_signal_startup 訊號處理方法;(linux 訊號以後再講。坑挖的太大了)

呼叫sapi_startup(&cgi_sapi_module),對sapi_model進行一些初始化工作

...
        sapi_startup(&cgi_sapi_module);
	fastcgi = fcgi_is_fastcgi();
	cgi_sapi_module.php_ini_path_override = NULL;
##
SAPI_API void sapi_startup(sapi_module_struct *sf)
{
	sf->ini_entries = NULL // ini_entries设置null
	sapi_module = *sf; // 把传进来的结构体赋值给sapi_module 
//  上面有关于sap_module的 定义   sapi_module_struct sapi_module;
//这里你可以理解为 初始化了sapi_module_struct 
 
#ifdef ZTS
	ts_allocate_fast_id(&sapi_globals_id, &sapi_globals_offset, sizeof(sapi_globals_struct), (ts_allocate_ctor) sapi_globals_ctor, (ts_allocate_dtor) sapi_globals_dtor);
# ifdef PHP_WIN32
	_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
# endif
#else
	sapi_globals_ctor(&sapi_globals);
#endif

#ifdef PHP_WIN32
	tsrm_win32_startup();
#endif

	reentrancy_startup();
}

#跟到sapi_starup 方法
static void sapi_globals_ctor(sapi_globals_struct *sapi_globals)
{
	memset(sapi_globals, 0, sizeof(*sapi_globals));
	zend_hash_init(&sapi_globals->known_post_content_types, 8, NULL, _type_dtor, 1);
	php_setup_sapi_content_types();
}

if條件暫且忽略 最後走到 sapi_globals_ctor函數

##sapi_globals_ctor()內部邏輯

下面是 memset() 函数的声明。
void *memset(void *str, int c, size_t n)

参数
str -- 指向要填充的内存块。
c -- 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。
n -- 要被设置为该值的字符数。
返回值
该值返回一个指向存储区 str 的指针。
#sapi_globals_ctor()內部邏輯

/* {{{ php_startup_sapi_content_types */
int php_startup_sapi_content_types(void)
{
	sapi_register_default_post_reader(php_default_post_reader);
	sapi_register_treat_data(php_default_treat_data);
	sapi_register_input_filter(php_default_input_filter, NULL);
	return SUCCESS;
}
memset 是c語言原生的初始化記憶體的方法

	sapi_startup(&cgi_sapi_module);
	fastcgi = fcgi_is_fastcgi();
	cgi_sapi_module.php_ini_path_override = NULL;

上面就新建一個大小560個位元組的記憶體

zend_hash_init  是定義一個hashtable(數組以後再講吧。)
php_setup_sapi_content_types內部實作是

int fcgi_is_fastcgi(void)
{
	if (!is_initialized) {
		return fcgi_init();
	} else {
		return is_fastcgi;
	}
}

最後一個reentrancy_startup是一個執行緒安全開啟的情況下才有效的函數
以上整個sapi_startup 方法執行完畢

int fcgi_init(void)
{
	if (!is_initialized) {
#ifndef _WIN32
		sa_t sa;
		socklen_t len = sizeof(sa);
#endif
		zend_hash_init(&fcgi_mgmt_vars, 8, NULL, fcgi_free_mgmt_var_cb, 1);
		fcgi_set_mgmt_var("FCGI_MPXS_CONNS", sizeof("FCGI_MPXS_CONNS")-1, "0", sizeof("0")-1);

		is_initialized = 1;
#ifdef _WIN32
# if 0
		/* TODO: Support for TCP sockets */
		WSADATA wsaData;

		if (WSAStartup(MAKEWORD(2,0), &wsaData)) {
			fprintf(stderr, "Error starting Windows Sockets.  Error: %d", WSAGetLastError());
			return 0;
		}
# endif
		if ((GetStdHandle(STD_OUTPUT_HANDLE) == INVALID_HANDLE_VALUE) &&
		    (GetStdHandle(STD_ERROR_HANDLE)  == INVALID_HANDLE_VALUE) &&
		    (GetStdHandle(STD_INPUT_HANDLE)  != INVALID_HANDLE_VALUE)) {
			char *str;
			DWORD pipe_mode = PIPE_READMODE_BYTE | PIPE_WAIT;
			HANDLE pipe = GetStdHandle(STD_INPUT_HANDLE);

			SetNamedPipeHandleState(pipe, &pipe_mode, NULL, NULL);

			str = getenv("_FCGI_SHUTDOWN_EVENT_");
			if (str != NULL) {
				zend_long ev;
				HANDLE shutdown_event;

				ZEND_ATOL(ev, str);
				shutdown_event = (HANDLE) ev;
				if (!CreateThread(NULL, 0, fcgi_shutdown_thread,
				                  shutdown_event, 0, NULL)) {
					return -1;
				}
			}
			str = getenv("_FCGI_MUTEX_");
			if (str != NULL) {
				zend_long mt;
				ZEND_ATOL(mt, str);
				fcgi_accept_mutex = (HANDLE) mt;
			}
			return is_fastcgi = 1;
		} else {
			return is_fastcgi = 0;
		}
#else
		errno = 0;
		if (getpeername(0, (struct sockaddr *)&sa, &len) != 0 && errno == ENOTCONN) {
			fcgi_setup_signals();
			return is_fastcgi = 1;
		} else {
			return is_fastcgi = 0;
		}
#endif
	}
	return is_fastcgi;
}

開始執行fcgi_is_fastcgi 方法
跟進去指向檔案fastcgi.c 檔案

rrreee

深入解析PHP8底層核心原始碼之SAPI(一)其中is_initialized 預設初始值為0

所以走到fcgi_init()函數

rrreee

初始化fgi協定相關內容

這篇寫的太流水帳了明天再補上吧先gdb分析cli模式

#本文經原作者PHP崔雪峰同意,發佈在php中文網,原文地址:https:/ /zhuanlan.zhihu.com/p/356037371

#推薦學習:《###PHP影片教學###》###

以上是深入解析PHP8底層核心原始碼之SAPI(一)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:知乎。如有侵權,請聯絡admin@php.cn刪除

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具