搜索
首页后端开发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%3A//www.laruence.com/2008/08/12/180.html

1.jpg

SAPI(Server Application Programimg Interface,服务端应用编程接口)相当于PHP外部环境的代理器。PHP可以应用在终端上,也可以应用在Web服务器中,应用在终端上的SAPI就叫作CLI SAPI,应用在Web服务器中的就叫作CGI SAPI。

他相当于一个中间层 或者叫他胶水 承上启下作用

sapi的核心定义 和宏文件 在 sapi.h中

2.jpg

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)

3.jpg

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多行

4.jpg

折叠一些 涉及ZTS (线程安全 之类的)的 代码

5.jpg

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_starup 方法

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();
}

if条件暂且忽略  最后走到  sapi_globals_ctor函数

sapi_globals_ctor()内部逻辑

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();
}

memset 是 c语言原生的初始化内存的方法

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

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

上面就新建一个大小560个字节的内存

zend_hash_init  是定义一个hashtable(数组以后再讲吧。。)

php_setup_sapi_content_types内部实现是

/* {{{ 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;
}

最后一个reentrancy_startup是一个线程安全开启的情况下才有效的函数

以上整个sapi_startup 方法执行完毕

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

开始执行fcgi_is_fastcgi 方法

跟进去 指向文件 fastcgi.c 文件

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

其中 is_initialized 默认初始值为0

6.jpg

所以走到fcgi_init()函数

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;
}

初始化 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中的所有内容
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器