标签归档:深入理解PHP内核

TIPI020200-SAPI概述

前一小节介绍了PHP的生命周期, 所有的请求都是通过SAPI接口实现的. 在源码的SAPI目录存放了PHP对各种服务器抽象层的代码,例如命令行程序的实现, mod_php的apache模块实现以及fastcgi的实现等等.

在各个服务器抽象层之间遵守着相同的约定,这里我们称之为SAPI接口。每个服务器都需要实现各自己的_sapi_module_struct结构中的各个方法。 然后在这个接口层之下,关于PHP的公共部分,全部通过这个结构体的相关方法调用实现。 如cgi模式和apache2服务器中的启动方法:

cgi_sapi_module.startup(&cgi_sapi_module)   //  cgi模式 cgi/cgi_main.c文件

apache2_sapi_module.startup(&apache2_sapi_module);  //  apache2服务器  apache2handler/sapi_apache2.c文件

除了startup方法,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)(TSRMLS_D);  // 激活
    int (*deactivate)(TSRMLS_D);    //  停用

    int (*ub_write)(const char *str, unsigned int str_length TSRMLS_DC);    //  不缓存的写操作(unbuffered write)
    void (*flush)(void *server_context);    //  flush
    struct stat *(*get_stat)(TSRMLS_D);     //  get uid
    char *(*getenv)(char *name, size_t name_len TSRMLS_DC); //  getenv

    void (*sapi_error)(int type, const char *error_msg, ...);   /* error handler */

    int (*header_handler)(sapi_header_struct *sapi_header, 
        sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC);   /* header handler */
    int (*send_headers)(sapi_headers_struct *sapi_headers TSRMLS_DC);   /* send headers handler */
    void (*send_header)(sapi_header_struct *sapi_header, void *server_context TSRMLS_DC);   /* send header handler */

    int (*read_post)(char *buffer, uint count_bytes TSRMLS_DC); /* read POST data */
    char *(*read_cookies)(TSRMLS_D);    /* read Cookies */

    void (*register_server_variables)(zval *track_vars_array TSRMLS_DC);    /* register server variables */
    void (*log_message)(char *message);     /* Log message */
    time_t (*get_request_time)(TSRMLS_D);   /* Request Time */
    void (*terminate_process)(TSRMLS_D);    /* Child Terminate */

    char *php_ini_path_override;    //  覆盖的ini路径

    ...
    ...
};

以上的这些结构在各服务器的接口实现中都有定义。如apache2的定义:

static sapi_module_struct apache2_sapi_module = {
    "apache2handler",
    "Apache 2.0 Handler",

    php_apache2_startup,                /* startup */
    php_module_shutdown_wrapper,            /* shutdown */

    ...
}

整个SAPI类似于一个面向对象中的模板方法模式的应用。SAPI.c和SAPI.h文件所包含的一些函数就是模板方法模式中的抽象模板,各个服务器对于sapi_module的定义及相关实现则是一个个具体的模板。只是这里没有继承。

作者:TIPI团队

TIPI0201–PHP生命周期和Zend引擎

一切的开始: SAPI接口

通常我们编写PHP Web程序都是通过Apache或者Nginx这类Web服务器来测试脚本. 或者在命令行下通过php程序来执行PHP脚本. 执行完成脚本后,服务器应答,浏览器显示应答信息,或者在命令结束后在标准输出显示内容. 我们很少关心PHP解释器在哪里. 虽然通过Web服务器和命令行程序执行 脚本看起来很不一样. 实际上她们的工作是一样的. 命令行程序和Web程序类似, 命令行参数传递给要执行的脚本,相当于通过url 请求一个php页面. 脚本戳里完成后返回响应结果,只不过命令行响应的结果是显示在终端上. 脚本执行的开始都是通过SAPI接口 进行的. 下一节将对SAPI进行更为深入的介绍.

开始和结束

PHP通过SAPI开始以后会经过两个主要的阶段. 处理请求之前的开始阶段和请求之后的结束阶段. 开始阶段有两个过程, 第一个是在 整个SAPI生命周期内(例如Apache启动以后的整个生命周期内或者命令行程序整个执行过程中)的开始阶段(MINIT),该阶段只进行一次. 第二个过程 发生在请求阶段,例如通过url请求某个页面.则在每次请求之前都会进行初始化过程(RINIT请求开始). 例如PHP注册了一些PHP模块,则在MINIT阶段会回调所有模块的MINIT函数. 模块在这个阶段可以进行一些初始化工作,例如注册常量, 定义 模块使用的类等等. 一般的模块回调函数

PHP_MINIT_FUNCTION(myphpextension)
{
    // 注册常量或者类等初始化操作
    return SUCCESS; 
}

请求到达之后PHP初始化执行脚本的基本环境,例如创建一个执行环境,包括保存php运行过程中变量名称和变量值内容的符号表. 以及当前所有 的函数以及类等信息的符号表. 然后PHP会调用所有模块RINIT函数, 在这个阶段各个模块也可以执行一些相关的操作, 模块的RINIT函数和MINIT函数类似

PHP_RINIT_FUNCTION(myphpextension)
{
    // 例如记录请求开始时间
    // 随后在请求结束的时候记录结束时间.这样我们就能够记录下处理请求所花费的时间了
    return SUCCESS; 
}

请求处理完后就进入了结束阶段, 一般脚本执行到末尾或者通过调用exit()或者die()函数,php都将进入结束阶段. 和开始阶段对应,结束阶段也分为 两个环节,一个在请求结束后(RSHUWDOWN),一个在SAPI生命周期结束时(MSHUTDOWN).

PHP_RSHUTDOWN_FUNCTION(myphpextension)
{
    // 例如记录请求结束时间, 并把相应的信息写入到日至文件中.
    return SUCCESS; 
}

想要了解扩展开发的相关内容,请参考第十三章 扩展开发

单进程SAPI生命周期

CLI/CGI模式的PHP属于单进程的SAPI模式. 这类的请求只处理一次请求就关闭. 也就是只会经过如下几个环节 开始 – 请求开始 – 请求关闭 – 结束 SAPI 接口就完成了其生命周期

多进程SAPI生命周期

通常PHP是编译为一个apache的模块来处理PHP请求的. 通常Apache会采用多进程模式, apache启动后fork出多个子进程, 每个进程的内存空间独立, 每个子进程都会经过开始和结束环节,不过每个进程的开始阶段只在进程fork出来以来后进行, 在整个进程的生命周期内可能会处理多个请求. 只有 在Apache关闭或者进程被结束之后才会进行关闭阶段,在这两个阶段之间会随着每个请求重复请求开始-请求关闭的环节.

多线程的SAPI生命周期

而在多线程模式下和多进程中的某个进程类似,不同的是在整个进程的生命周期内会并行的重复着 请求开始-请求关闭的环节

Zend引擎

Zend引擎作为PHP实现的核心,提供了语言实现上的基础设施.例如: PHP的语法实现, 脚本的编译运行环境, 扩展机制以及内存管理等, 可以说Zend引擎是PHP的核心, 当然这里的PHP指的是官方的PHP实现(除了官方的实现,目前比较知名的有facebook的hiphop实现,不过到目前为止,PHP还没有一个标准的语言规范), 而PHP则提供了请求处理和其他Web服务器的接口(SAPI).

参考文献


Extending and Embedding PHP

作者:TIPI团队

TIPI0102–PHP源码结构、阅读代码方法

第二节 PHP源码结构、阅读代码方法

PHP源码目录结构:


俗话讲:大巧不工。PHP的源码在结构上非常清晰甚至简单。下面,先简单介绍一下PHP源码的目录结构。

  • build: 源码编译相关文件,包括buildconft等sh文件,还有一些awk的脚本。
  • ext 官方扩展目录,包括了绝大多数PHP的函数的定义和实现,如array系列,pdo系列,spl系列等函数的实现,都在此处相对的子目录中。
  • main PHP宏定义与实现,在需要扩展PHP时,经常要使用的PHP_*系列宏就在这里定义。
  • Zend 包含Zend引擎文件,Zend API宏的定义和实现。
  • pear “PHP 扩展与应用仓库”, 包含PEAR的核心文件。
  • sapi 包含了各种服务器抽象层的代码,以目录区分。
  • TSRM “线程安全资源管理器” (TSRM) 目录。
  • tests 测试脚本目录。
  • win32 Windows 下编译PHP相关的脚本。

PHP源码阅读方法:


使用VIM + Ctags查看并追踪源码:

VIM是一个非常给力的编辑器,在纯命令终端下,它几乎是无可替代的(Emacs?)。
ctags可以将源代码中的各种函数、宏等信息做上标记。这样,使用VIM就可以很方便的查看源码。
简洁使用说明:

#在PHP源码目录(假定为/server/php-src)执行:
$ cd /server/php-src
$ ctags -R

#在~/.vimrc中添加:
set tags+=/server/php-src/tags

再用vim打开各种php源码文件时,将光标移到想查看的函数、宏、变量上面, 使用 Ctrl+p 就可以自动跳转至定义,Ctrl+o 可以返回上一次查看位置。

使用Visual Studio + editplus查看并追踪源码:

看源码还是用IDE舒服一些,windows下我们还是用Visual Studio 2010看吧。 在win32目录下已经存在了可以直接打开的工程文件,如果由于版本原因无法打开,可以在此源码目录上新建一个基于现有文件的Win32 Console Application工程。
常用快捷键

F12 转到定义
CTRL + F12转到声明

F3: 查找下一个
Shift+F3: 查找上一个

Ctrl+G: 转到指定行

CTRL + -向后定位
CTRL + SHIFT + -向前定位

对于一些搜索类的操作,可以考虑使用editplus或其它文本编辑工具进行,这样的搜索速度相对来说会快一些。
如果使用editplus进行搜索,一般是选择 【搜索】 中的 【在文件中查找…】

作者:TIPI Team