Home > Article > Backend Development > In-depth analysis of SAPI, the underlying kernel source code of PHP8 (1)
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.
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
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
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)
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
Fold some code involving ZTS (thread safety and the like)
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
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!