Home  >  Article  >  Backend Development  >  In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1)

In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1)

青灯夜游
青灯夜游forward
2021-06-10 14:54:122706browse

This article will give you an in-depth analysis of the underlying kernel source code of PHP8 and learn about SAPI. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to everyone.

In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1)

Recommended related articles: "Analysis of PHP8 underlying kernel source code - array (1)"

in The following environment is built under 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]#

Introduce a picture of Brother Bird

In-depth understanding of Zend SAPIs (Zend SAPI Internals)

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

In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1)

SAPI (Server Application Programimg Interface, server application programming interface) is equivalent to the proxy of PHP's external environment. PHP can be applied on the terminal or in the Web server. The SAPI applied on the terminal is called CLI SAPI, and the SAPI applied on the Web server is called CGI SAPI.

It is equivalent to an intermediate layer or a glue that connects the previous and the following.

The core definition and macro files of sapi are in sapi.h

In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1)

There is an important structure in 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
};

It "inherits" from the structure_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);
};

After commenting, it is as follows

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

This The structure is very critical. In the CLI life cycle, the function pointers defined in it will be called to implement their respective functions

There is also an important data structure-sapi_globals, and its corresponding macro is SG(v)

In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1)

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;

This SG has a total of 560 bytes and has been used many times throughout the PHP life cycle

Looking back at cgi_main.c, the entire cgi code has a total of More than 900 lines

In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1)

Fold some code involving ZTS (thread safety and the like)

In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1)

zend_signal_startup Signal processing method; (Linux signals will be discussed later. The pit is too big)

Call sapi_startup(&cgi_sapi_module) to perform some initialization work on sapi_model

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

Follow the sapi_starup method

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

Ignore the if condition for now and finally go to the sapi_globals_ctor function

sapi_globals_ctor() internal logic

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 is the native method of initializing memory in C language

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

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

The above will create a new memory of 560 bytes

zend_hash_init is to define a hashtable (array Let’s talk about it later...)

The internal implementation of php_setup_sapi_content_types is

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

The last reentrancy_startup is a function that is only valid when thread safety is turned on

The entire sapi_startup method above has been executed

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

Start executing the fcgi_is_fastcgi method

Follow up and point to the file fastcgi.c file

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

The default initial value of is_initialized is 0

In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1)

So go to the fcgi_init() function

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

Initialize fgi protocol related content

This article is too long to write. I will make up for it tomorrow. First analyze the cli mode with gdb.

This article was approved by the original author PHP Cui Xuefeng and published on the PHP Chinese website. The original address is: https:/ /zhuanlan.zhihu.com/p/356037371

Recommended learning: "PHP Video Tutorial"

The above is the detailed content of In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:zhihu.com. If there is any infringement, please contact admin@php.cn delete