学习使用的PHP是7.2.6
我们在源码安装好PHP之后查看当前的目录:
其中SAPI就是我们这篇文章所要学习的内容。那么什么是SAPI呢?
SAPI全称为Server Application Programming Interface,服务器应用编程接口
我们都知道PHP是一个脚本解释器,提供脚本解析与执行,我们可以在不同环境中应用这个解析器,例如web环境中,命令行中,嵌入其他应用中,面对着这么多不同的环境,PHP提供了一个SAPI层以适配不同的应用环境,SAPI是整个PHP架构最外层的一部分,主要负责PHP的初始化工作,如下是鸟哥给出的PHP架构图,大家可以点此链接到鸟哥的文章
在学习之前,我们先大致了解PHP的生命周期,他大概被划分为以下几个阶段:
如上因为不同的SAPI会有差异,例如CLI下每次执行脚本都会走一遍完整的流程(不会有请求关闭到请求开始的那条线),而在FastCgi模式下则会在启动时执行一次模块初始化,然后在各个请求只经历请求初始化,执行脚本,请求关闭。在SAPI关闭时经历模块关闭阶段。
下面以cli模式来学习SAPI机制:
cli SAPI的main函数在/sapi/cli/php_cli.c中,执行时首先解析命令行参数,然后初始化sapi_module_struct,这是一个结构体,记录SAPI信息的主要结构,代码如下:
static sapi_module_struct cli_sapi_module = { "cli", /* name */ "Command Line Interface", /* pretty name */ php_cli_startup, /* startup */ php_module_shutdown_wrapper, /* shutdown */ NULL, /* activate */ sapi_cli_deactivate, /* deactivate */ sapi_cli_ub_write, /* unbuffered write */ sapi_cli_flush, /* flush */ NULL, /* get uid */ NULL, /* getenv */ php_error, /* error handler */ sapi_cli_header_handler, /* header handler */ sapi_cli_send_headers, /* send headers handler */ sapi_cli_send_header, /* send header handler */ NULL, /* read POST data */ sapi_cli_read_cookies, /* read Cookies */ sapi_cli_register_variables, /* register server variables */ sapi_cli_log_message, /* Log message */ NULL, /* Get request time */ NULL, /* Child terminate */ STANDARD_SAPI_MODULE_PROPERTIES };
其中sapi_module_struct定义在main/SAPI.h中,代码如下:
struct _sapi_module_struct { char *name; // 应用层名称 char *pretty_name; // 应用层更易读的名字 int (*startup)(struct _sapi_module_struct *sapi_module); // 当一个应用要调用php的时候,这个模块启动的时候会调用的函数 int (*shutdown)(struct _sapi_module_struct *sapi_module); // 当一个应用要调用php的时候,这个模块结束的时候会调用的函数 int (*activate)(void); // 在处理每个request的时候,激活需要调用的函数 int (*deactivate)(void); // 在处理完每个request的时候,收尾时候要调用的函数 size_t (*ub_write)(const char *str, size_t str_length); // 这个函数告诉php如何输出数据 void (*flush)(void *server_context); // 提供给php的刷新缓存的函数指针 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); // TODO: 调用exit的时候调用的方法 char *php_ini_path_override; // PHP的ini文件被复写了所复写的地址 void (*default_post_reader)(void); // 这里和前面的read_post有个差别,read_post负责如何获取POST数据,而这里的函数负责如何解析POST数据 void (*treat_data)(int arg, char *str, zval *destArray); // 对数据进行处理,比如进行安全过滤等。 default_post_reader/tread_data/input_filter是三个能对输入进行过滤和处理的函数 char *executable_location; // 执行的地理位置 int php_ini_ignore; // 是否不使用任何ini配置文件,比如php -n 就将这个位置设置为1 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); // 默认的ini配置 int phpinfo_as_text; // 是否打印phpinfo信息 char *ini_entries; // 有没有附带的ini配置,比如使用php -d date.timezone=America/Adak,可以在命令行中设置时区 const zend_function_entry *additional_functions; // 每个SAPI模块特有的一些函数注册,比如cli的cli_get_process_title unsigned int (*input_filter_init)(void); // TODO: };
一个PHP命令行应用调用时,经历了以上步骤之后,就会进入module startup阶段
/* startup after we get the above ini override se we get things right */ if (sapi_module->startup(sapi_module) == FAILURE) { /* there is no way to see if we must call zend_ini_deactivate() * since we cannot check if EG(ini_directives) has been initialised * because the executor's constructor does not set initialize it. * Apart from that there seems no need for zend_ini_deactivate() yet. * So we goto out_err.*/ exit_status = 1; goto out; }
而cli_sapi_module变量定义的startup函数是php_cli_startup(),这个函数代码十分简单,直接调用php_module_startup,代码如下:
static int php_cli_startup(sapi_module_struct *sapi_module) /* {{{ */ { if (php_module_startup(sapi_module, NULL, 0)==FAILURE) { return FAILURE; } return SUCCESS; }
而php_module_startup()函数定义在main/main.c中,由于代码量太大就不帖出来;
可以看到上面代码调用了php_module_startup函数,他定义在main/main.c中,主要功能是:
1、激活SAPI
2、启动php输出:php_output_startup
3、初始化垃圾回收器:gc_globals_ctor,分配zend_gc_globals
4、启动zend引擎,zend_startup()
5、注册PHP定义的常量
6、解析php.ini
7、注册用于获取$_GET,$_POST,$_COOKIE,$_SERVER,$_ENV,$_REQUEST,$FILES变量的handleer
8、注册静态编译的扩展
9、注册动态加载的扩展
在完成了module startup之后就会进入请求初始化阶段:
zend_first_try { #ifndef PHP_CLI_WIN32_NO_CONSOLE if (sapi_module == &cli_sapi_module) { #endif exit_status = do_cli(argc, argv); #ifndef PHP_CLI_WIN32_NO_CONSOLE } else { exit_status = do_cli_server(argc, argv); } #endif } zend_end_try();、
在这里do_cli()函数将完成请求的处理,它会在一开始对使用到的命令行参数进行解析,如果是一些查询之类的请求,例如(-v,-m),则不会经历PHP的请求生命周期,当脚本执行完成之后就会退出do_cli()回到main中进入module_shutdown阶段
学习的比较浅,有什么不对的地方不吝请教,这样才会有进步^^
参考链接:
《php7内核剖析》
http://www.laruence.com/2008/08/12/180.html
https://www.cnblogs.com/yjf512/p/6084963.html