PHP学习之SAPI

学习使用的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

猜你喜欢

转载自blog.csdn.net/m0_37752084/article/details/80771951