首頁 > 後端開發 > PHP8 > 主體

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

青灯夜游
發布: 2023-02-17 12:00:02
轉載
2713 人瀏覽過

這篇文章給大家深入解析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中文網其他相關文章!

相關標籤:
來源:zhihu.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門推薦
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板