zoukankan      html  css  js  c++  java
  • PHP7内核(四):生命周期之开始前的躁动

    上一章我们对PHP的源码目录结构有了初步了解,本章我们继续从生命周期的维度对PHP进行剖析。

    一、概览

    生命周期是什么呢?你可以把它看作执行过程,PHP的生命周期也就是它从开始执行到结束执行的过程。

    PHP生命周期有五个阶段,分别为模块初始化阶段、请求初始化阶段、执行阶段、请求关闭阶段、模块关闭阶段。只是不同SAPI模式下,请求的情况略有不同,比如FastCGI下只经历了一次模块初始化阶段,接下来所有请求只经历请求初始化、执行脚本、请求关闭阶段。

    image

    在初步了解生命周期的五个阶段之后,我们先来讲述在进入模块初始化阶段(php_module_startup)之前PHP所做的工作(本文继续以PHP7.0.12版本的CLI模式)。好了,我们现在开始。

    二、源码分析

    2.1、sapi_module_struct

    cli模式下的入口文件是sapi/cli/php_cli.c,打开该文件,定位到主函数main,有没有觉得1180行出现的结构体sapi_module_struct很眼熟?这就是上篇文章SAPI的介绍中提到到结构体,它是扩展PHP对外服务的关键。

    //sapi/cli/php_cli.c
    sapi_module_struct *sapi_module = &cli_sapi_module;
    

    先来看一下sapi_module_structmain/SAPI.h中的定义:

    //main/SAPI.h
    struct _sapi_module_struct {
    	char *name; //名字,如cli、fpm等
    	char *pretty_name;  //更容易理解的名字
    
    	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, ...);   //错误处理函数指针
    
    	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); //输出错误信息函数指针
    	double (*get_request_time)(void);   //获取请求时间的函数指针
    	void (*terminate_process)(void);    //调用exit退出时的函数指针
    
    	char *php_ini_path_override;    //PHP的ini文件被复写的地址
    
    	void (*block_interruptions)(void);  
    	void (*unblock_interruptions)(void);
    
    	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); //强制使用http1.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);
    	int phpinfo_as_text; //是否输出phpinfo信息 //默认的ini配置的函数指针,把ini配置信息在HashTable中
    
    	char *ini_entries; //执行时附带的ini配置,可以使php -d设置
    	const zend_function_entry *additional_functions; //每个SAPI模块特有的一些函数注册,比如cli的cli_get_process_title
    	unsigned int (*input_filter_init)(void);
    }
    

    SAPI下的每一个模式都实现了该结构体,比如在CLI中:

    //sapi/cli/php_cli.c
    static sapi_module_struct cli_sapi_module = {
    	"cli",							/* name */
    	"Command Line Interface",    	/* pretty name */
    	......
    }
    

    FPM中:

    //sapi/fpm/fpm/fpm_main.c
    static sapi_module_struct cgi_sapi_module = {
    	"fpm-fcgi",						/* name */
    	"FPM/FastCGI",					/* pretty name */
        ......
    }
    

    在litespeed中也有相同定义:

    //sapi/litespeed/lsapi_main.c
    static sapi_module_struct lsapi_sapi_module =
    {
        "litespeed",
        "LiteSpeed V6.10",
        ......
    }
    

    2.2、sapi_startup

    我们继续往下看,在经过一系列变量的初始化后,于1302行又调用了sapi_startup函数。

    //sapi/cli/php_cli.c
    sapi_startup(sapi_module);
    

    该函数定义了sapi_globals_struct,也就是我们常说的SG宏,它的主要作用是保存请求的基本信息,比如服务器信息、header、编码等。

    //main/SAPI.h
    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;
    

    2.3、sapi_module->startup

    我们继续往下看,sapi_module调用了startup函数:

    //sapi/cli/php_cli.c
    if (sapi_module->startup(sapi_module) == FAILURE) {
    	exit_status = 1;
    	goto out;
    }
    

    然后又调用了CLI在sapi_module_struct中定义的startup对应的钩子函数php_cli_startup

    //sapi/cli/php_cli.c
    static sapi_module_struct cli_sapi_module = {
    	"cli",							/* name */
    	"Command Line Interface",    	/* pretty name */
    
    	php_cli_startup,				/* startup */
    	......
    }
    

    继续跟进,php_cli_startup函数中又调用了php_module_startup函数:

    //sapi/cli/php_cli.c
    static int php_cli_startup(sapi_module_struct *sapi_module) /* {{{ */
    {
    	if (php_module_startup(sapi_module, NULL, 0)==FAILURE) {
    		return FAILURE;
    	}
    	return SUCCESS;
    }
    

    2.4、php_module_startup

    是不是很眼熟,这不就是模块初始化阶段的函数嘛!原来执行了这么久才到我们的关键点,模块初始化阶段内容比较多,我们通过下一章进行详细剖析。

    //main/main.c
    int php_module_startup(sapi_module_struct *sf, zend_module_entry *additional_modules, uint num_additional_modules)
    {
    	zend_utility_functions zuf;
    	zend_utility_values zuv;
    	int retval = SUCCESS, module_number=0;	/* for REGISTER_INI_ENTRIES() */
    	char *php_os;
    	zend_module_entry *module;
    	...
    }
    

    注意:我在本文贴出的代码都标识了文件位置,我们可以看出来,在PHP五大生命周期开始之前一直都是在sapi目录中执行的,而从php_module_struct也就是模块初始化阶段开始,才执行到了main目录,这意味着PHP的生命周期的第一个阶段是从main目录下开始的。

  • 相关阅读:
    485串口接线
    mvc3 升级mvc5
    VB连接ACCESS数据库,使用 LIKE 通配符问题
    VB6 读写西门子PLC
    可用的 .net core 支持 RSA 私钥加密工具类
    解决 Win7 远程桌面 已停止工作的问题
    解决 WinForm 重写 CreateParams 隐藏窗口以后的显示问题
    解决安装 .net framework 发生 extracting files error 问题
    CentOS7 安装配置笔记
    通过特殊处理 Resize 事件解决 WinForm 加载时闪烁问题的一个方法
  • 原文地址:https://www.cnblogs.com/pingyeaa/p/9567318.html
Copyright © 2011-2022 走看看