您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

深入解析PHP8底层内核源码之SAPI(一)

2026/2/12 6:29:29发布12次查看
本篇文章给大家深入解析php8底层内核源码,了解一下sapi。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。
相关文章推荐:《解析php8底层内核源码-数组(一)》
在docker下 搭建里如下的环境
[root@a951700e857d cui-php]# php -vphp 8.0.2 (cli) (built: mar 2 2021 02:40:03) ( nts )copyright (c) the php groupzend 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
sapi(server application programimg interface,服务端应用编程接口)相当于php外部环境的代理器。php可以应用在终端上,也可以应用在web服务器中,应用在终端上的sapi就叫作cli sapi,应用在web服务器中的就叫作cgi sapi。
他相当于一个中间层 或者叫他胶水 承上启下作用
sapi的核心定义 和宏文件 在 sapi.h中
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)
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多行
折叠一些 涉及zts (线程安全 之类的)的 代码
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
所以走到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崔雪峰同意,发布在,原文地址:https://zhuanlan.zhihu.com/p/356037371
推荐学习:《php视频教程》
以上就是深入解析php8底层内核源码之sapi(一)的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product