• 技术文章 >后端开发 >PHP8

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

    青灯夜游青灯夜游2021-06-10 14:54:12转载211
    本篇文章给大家深入解析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删除
    专题推荐:PHP8 SAPI
    上一篇:解析PHP8底层内核源码-数组(二) 下一篇:解析PHP8底层内核源码-数组(三)
    VIP会员

    相关文章推荐

    • Ubuntu20.04/18.04下安装或更新至PHP8• WordPress 插件、主题与PHP8的兼容性• 开启Laravel对PHP8的支持• PHP8新特性解读(开发代码实例演示)• 聊聊PHP8的一些语法新特性• 解析PHP8底层内核源码-数组(一)• 解析PHP8底层内核源码-数组(二)

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网