PHP

PHP扩展开发

PHP扩展开发入门

Posted by Bug1024 on May 27, 2017

PHP生命周期

  • 模块初始化阶段
    • 注册常量
    • 注册核心php.ini
    • 注册GET、POST等全局变量
    • 注册内部核心扩展
    • 根据php.ini注册加载其他扩展
    • 调用扩展的PHP_MINIT()
  • 请求初始化阶段
    • 初始化编译器
    • 初始化EG、全局符号表、include file数组、词法分析器
    • 初始化SG
    • 调用扩展的PHP_RINIT()
  • 脚本执行阶段
    • 编译opcode
    • 执行zvm
  • 请求关闭阶段
    • 调用register_shutdown_fucntion()
    • 清理EG
    • 销毁全局变量PG
    • 调用扩展的PHP_RSHUTDOWN()
    • 关闭编译器、执行器、内存管理器
  • 模块关闭阶段
    • 调用扩展的PHP_MSHUTDOWN()
    • 清理ini HashTable元素
    • 关闭output
    • 释放PG

内核基础-变量

变量存储结构

    typedef struct _zval_struct zval;
    ...
    struct _zval_struct {
        // 变量值
        zvalue_value value;
        // 引用计数
        zend_uint refcount__gc;
        // 变量类型
        zend_uchar type;
        // 是否引用
        zend_uchar is_ref__gc;
    };

变量值存储结构

    // 使用联合体提升空间利用率
    typedef union _zvalue_value {
        long lval;
        double dval;
        // 存储字符串时多了一个字符串长度的字段
        struct {
            char *val;
            int len;
        } str;
        // 数组Array基于此实现,通常使用拉链法来避免值冲突
        HashTable *ht;
        // 对象Object,PHP的对象只有在运行时才会被创建
        zend_object_value obj;
    } zvalue_value;
  • __FILE__等常量其实相当于一个常量模板,或者说是一个占位符,在词法解析时这些模板或占位符就被替换成实际的值

  • GET、POST这些变量是存储在一个集中的地方:EG(symbol_table)

内核基础-函数和类

PHP函数

    PHP_FUNCTION(count) {
        zval *array;
        long mode = COUNT_NORMAL;
        // 参数个数 + 宏 + 参数类型 + 接收参数的指针
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &array, &mode) == FAILURE) {
            return;
        }
        ... //省略

        // 返回值,其余的有RETURN_DOUBLE、RETURN_STRING、RETURN_BOOL、RETURN_NULL、RETURN_EMPTY_STRING、RETURN_REAOURCE
        RETURN_LONG
    }

PHP类实现

    struct _zend_class_entry {
        // 类型:ZEND_INTERNAL_CLASS / ZEND_USER_CLASS
        char type;
        // 类名称
        char *name;
        // 即sizeof(name) - 1
        zend_uint name_length;
        // 继承的父类
        struct _zend_class_entry *parent;
        // 引用数
        int refcount;
        zend_bool constants_updated;

        zend_uint ce_flags; // ZEND_ACC_IMPLICIT_ABSTRACT_CLASS: 类存在abstract方法
        // ZEND_ACC_EXPLICIT_ABSTRACT_CLASS: 在类名称前加了abstract关键字
        // ZEND_ACC_FINAL_CLASS
        // ZEND_ACC_INTERFACE
        HashTable function_table;      // 方法
        HashTable default_properties;  // 默认属性
        HashTable properties_info;     // 属性信息
        HashTable default_static_members;// 类本身所具有的静态变量
        HashTable *static_members; // type == ZEND_USER_CLASS时,取&default_static_members;
        // type == ZEND_INTERAL_CLASS时,设为NULL
        HashTable constants_table; // 常量
        struct _zend_function_entry *builtin_functions;// 方法定义入口

        union _zend_function *constructor;
        union _zend_function *destructor;
        union _zend_function *clone;

        // 魔术方法
        union _zend_function *__get;
        union _zend_function *__set;
        union _zend_function *__unset;
        union _zend_function *__isset;
        union _zend_function *__call;
        union _zend_function *__tostring;
        union _zend_function *serialize_func;
        union _zend_function *unserialize_func;
        // 迭代
        zend_class_iterator_funcs iterator_funcs;

        // 类句柄
        zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC);
        zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, intby_ref TSRMLS_DC);

        // 类声明的接口
        int(*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC);

        // 序列化回调函数指针
        int(*serialize)(zval *object unsignedchar**buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC);
        int(*unserialize)(zval **object, zend_class_entry *ce, constunsignedchar*buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC);

        // 类实现的接口
        zend_class_entry **interfaces;
        // 类实现的接口数
        zend_uint num_interfaces;
        // 类的存放文件地址 绝对地址
        char *filename;
        // 类定义的开始行
        zend_uint line_start;
        // 类定义的结束行
        zend_uint line_end;
        char *doc_comment;
        zend_uint doc_comment_len;
        // 类所在的模块入口:EG(current_module)
        struct _zend_module_entry *module;
    };

函数和方法的异同 函数和方法都是在编译阶段注册到compiler_globals变量中的,二者都使用相同的内核处理函数zend_do_begin_function_declaration() 和zend_do_end_function_declaration()来完成这一过程,不同的的地方在于定义(注册)的实现和调用的实现

编写扩展的基本步骤

  • 下载php-src源码
  • 通过ext目录下ext_skel脚本生成扩展的基本框架:./ext_skel –extname=your_extname;
  • 修改config.m4配置:设置编译配置参数、设置扩展的源文件、依赖库/函数检查等等;
  • 编写扩展要实现的功能:按照PHP扩展的格式以及PHP提供的API编写功能;
  • 生成configure:扩展编写完成后执行phpize脚本生成configure及其它配置文件;
  • 编译&安装:./configure、make、make install,然后将扩展的.so路径添加到php.ini中。

扩展中参数的解析

使用zend_parse_parameters方法进行解析,PHP7中推荐使用 Fast Parameter Parsing API

    PHP_FUNCTION(array_slice) {
    // 省略...
    #ifndef FAST_ZPP
        if (zend_parse_parameters(ZEND_NUM_ARGS(), "al|zb", &input, &offset, &z_length, &preserve_keys) == FAILURE) {
            return;
        }
    #else
        ZEND_PARSE_PARAMETERS_START(2, 4) // 最少参数数和最多参数数
            Z_PARAM_ARRAY(input)
            Z_PARAM_LONG(offset)
            Z_PARAM_OPTIONAL
            Z_PARAM_ZVAL(z_length)
            Z_PARAM_BOOL(preserve_keys)
        ZEND_PARSE_PARAMETERS_END();
    #endif
    // 省略...
    }

PHP进程通信

  • pcntl扩展:主要的进程扩展,完成进程的创建,子进程的创建,也是当前使用比较广的多进程
  • posix扩展:完成posix兼容机通用api,如获取进程id,杀死进程等。主要依赖 IEEE 1003.1 (POSIX.1) ,兼容posi
  • sysvmsg扩展:实现system v方式的进程间通信之消息队列
  • sysvsem扩展:实现system v方式的信号量
  • sysvshm扩展:实现system v方式的共享内存
  • sockets扩展:实现socket通信,跨机器,跨平台
  • php也有一些封装好的异步进程处理框架:例如swoole,workman等

Zend内存池

zend针对内存的操作封装了一层,用于替换直接的内存操作:malloc、free等,实现了更高效率的内存利用,其实现主要参考了tcmalloc的设计。 内存池是内核中最底层的内存操作,定义了三种粒度的内存块:chunk、page、slot,每个chunk的大小为2M,page大小为4KB,一个chunk被切割为512个page,而一个或若干个page被切割为多个slot,所以申请内存时按照不同的申请大小决定具体的分配策略:

  • Huge(chunk): 申请内存大于2M,直接调用系统分配,分配若干个chunk
  • Large(page): 申请内存大于3092B(3/4 page_size),小于2044KB(511 page_size),分配若干个page
  • Small(slot): 申请内存小于等于3092B(3/4 page_size),内存池提前定义好了30种同等大小的内存(8,16,24,32,…3072),他们分配在不同的page上(不同大小的内存可能会分配在多个连续的page),申请内存时直接在对应page上查找可用位置

未完待续。。。