聊聊php扩展架构

        作为一名 phper, 对 php 扩展是如何实现的一直都有点好奇,今天就来看看 php 扩展的架构大概是什么样子的。随意看了几个扩展源码,发现 json 扩展的源码实现还是蛮简单的。不过再看它之前,咱们先自己生成一个扩展来看看。

[root@iz2ze9ryeo103b85y5iy2wz ext]# pwd
/usr/local/src/php/ext
[root@iz2ze9ryeo103b85y5iy2wz ext]# ./ext_skel --extname=lucas

        执行以上命令后,便在当前目录下生成一个 lucas 文件夹。进去后会发现一个与扩展同名的 c 文件。它就是一个扩展最原始的架构,省略部分后大概如下:

// lucas_functions[] Every user visible function must have an entry in lucas_functions[].
const zend_function_entry lucas_functions[] = {
    PHP_FE(confirm_lucas_compiled,  NULL)       /* For testing, remove later. */
    PHP_FE_END  /* Must be the last line in lucas_functions[] */
};

// Every user-visible function in PHP should document itself in the source */
// proto string confirm_lucas_compiled(string arg) Return a string to confirm that the module is compiled in
PHP_FUNCTION(confirm_lucas_compiled)
{
    ...
}

PHP_MINIT_FUNCTION(lucas)
{
    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(lucas)
{
    return SUCCESS;
}

// Remove if there's nothing to do at request start
PHP_RINIT_FUNCTION(lucas)
{
    return SUCCESS;
}

// Remove if there's nothing to do at request end
PHP_RSHUTDOWN_FUNCTION(lucas)
{
    return SUCCESS;
}

// lucas_module_entry
zend_module_entry lucas_module_entry = {
    ...
};

#ifdef COMPILE_DL_LUCAS
ZEND_GET_MODULE(lucas)
#endif

        按顺序大致包含以下几个部分:

  1. 声明 zend 函数块
  2. 实现导出函数
  3. 模块初始化
  4. 关闭模块
  5. 请求初始化
  6. 关闭请求
  7. 声明 zend 模块
  8. 实现 get_module() 函数

        咱们现在就借助 json 扩展的对应源码来看下每部分都干了啥。

一、声明 zend 函数块

static const zend_function_entry json_functions[] = { 
    PHP_FE(json_encode, arginfo_json_encode)
    PHP_FE(json_decode, arginfo_json_decode)
    PHP_FE(json_last_error, arginfo_json_last_error)
    PHP_FE(json_last_error_msg, arginfo_json_last_error_msg)
    PHP_FE_END
};

        先考虑一个宏观问题,函数在扩展中怎么引入到 zend 引擎,然后在 php 代码中调用呢?譬如常用的 json_encode() 和 json_decode() 函数。这里就要用到 zend_function_entry 结构体:

typedef struct _zend_function_entry {
    char *fname;    //指定在 php 脚本里调用的函数名
    void (*handler) (INTERNAL_FUNCTION_PARAMETERS);    //指向对应c函数的句柄,就是声明的导出函数句柄
    unsigned char *func_arg_types;    //标识一些参数是否要强制性地按引用方式进行传递
} zend_function_entry;

        zend 引擎在载入扩展时,会自动把这个结构体数组中的所有函数引入到函数表中,这样 php 脚本就可以直接调用这些函数了。

二、实现导出函数

        在确定函数能在php脚本中被调用以后,就考虑根据需要实现函数功能了。譬如 json_encode() 函数实现如下:

static PHP_FUNCTION(json_encode)
{
    zval *parameter;
    php_json_encoder encoder;
    smart_str buf = {0};
    zend_long options = 0;
    zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH;

    ZEND_PARSE_PARAMETERS_START(1, 3)
        Z_PARAM_ZVAL(parameter)
        Z_PARAM_OPTIONAL
        Z_PARAM_LONG(options)
        Z_PARAM_LONG(depth)
    ZEND_PARSE_PARAMETERS_END();

    php_json_encode_init(&encoder);
    encoder.max_depth = (int)depth;
    php_json_encode_zval(&buf, parameter, (int)options, &encoder);
    JSON_G(error_code) = encoder.error_code;

    if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) {
        smart_str_free(&buf);
        RETURN_FALSE;
    }

    smart_str_0(&buf); /* copy? */
    if (buf.s) {
        RETURN_NEW_STR(buf.s);
    }
    RETURN_EMPTY_STRING();
}

        实现导出函数是通过 PHP_FUNCTION 宏来定义的,然后在函数体中实现想要的功能。并且在 zend 函数块中声明的所有函数都必须实现,否则会出现严重错误。

三、模块初始化

static PHP_MINIT_FUNCTION(json)
{
    zend_class_entry ce;

    INIT_CLASS_ENTRY(ce, "JsonSerializable", json_serializable_interface);
    php_json_serializable_ce = zend_register_internal_interface(&ce);

    /* options for json_encode */
    PHP_JSON_REGISTER_CONSTANT("JSON_HEX_TAG",  PHP_JSON_HEX_TAG);
    PHP_JSON_REGISTER_CONSTANT("JSON_HEX_AMP",  PHP_JSON_HEX_AMP);
    PHP_JSON_REGISTER_CONSTANT("JSON_HEX_APOS", PHP_JSON_HEX_APOS);
    PHP_JSON_REGISTER_CONSTANT("JSON_HEX_QUOT", PHP_JSON_HEX_QUOT);
    PHP_JSON_REGISTER_CONSTANT("JSON_FORCE_OBJECT", PHP_JSON_FORCE_OBJECT);
    PHP_JSON_REGISTER_CONSTANT("JSON_NUMERIC_CHECK", PHP_JSON_NUMERIC_CHECK);
    PHP_JSON_REGISTER_CONSTANT("JSON_UNESCAPED_SLASHES", PHP_JSON_UNESCAPED_SLASHES);
    PHP_JSON_REGISTER_CONSTANT("JSON_PRETTY_PRINT", PHP_JSON_PRETTY_PRINT);
    PHP_JSON_REGISTER_CONSTANT("JSON_UNESCAPED_UNICODE", PHP_JSON_UNESCAPED_UNICODE);
    PHP_JSON_REGISTER_CONSTANT("JSON_PARTIAL_OUTPUT_ON_ERROR", PHP_JSON_PARTIAL_OUTPUT_ON_ERROR);
    PHP_JSON_REGISTER_CONSTANT("JSON_PRESERVE_ZERO_FRACTION", PHP_JSON_PRESERVE_ZERO_FRACTION);
    PHP_JSON_REGISTER_CONSTANT("JSON_UNESCAPED_LINE_TERMINATORS", PHP_JSON_UNESCAPED_LINE_TERMINATORS);
    ...

    return SUCCESS;
}

        从这串代码中可以看到,json_encode() 函数中的第二个参数引用的常量都是在这定义的。可以看出在该函数中可以做些初始化的工作,如定义常量等。

四、关闭模块

        这块没啥好说的,做一些收尾工作。

五、请求初始化

        这块 json 扩展并没有相应的实现部分,但根据其它扩展的实现部分,这块主要是当请求到达后,php 会初始化执行脚本的基本环境。还可以记录调用该部分的开始时间,随后在请求结束时候记录结束时间,这样就能够记录下处理请求所花费的时间了。

六、关闭请求

略。

七、声明 zend 模块

zend_module_entry json_module_entry = { 
    STANDARD_MODULE_HEADER,
    "json",
    json_functions,
    PHP_MINIT(json),
    NULL,
    NULL,
    NULL,
    PHP_MINFO(json),
    PHP_JSON_VERSION,
    PHP_MODULE_GLOBALS(json),
    PHP_GINIT(json),
    NULL,
    NULL,
    STANDARD_MODULE_PROPERTIES_EX
};

        php 扩展的信息是保存在 zend_module_entry 结构体中的,它包含了所有需要向 zend 引擎提供的模块信息。定义如下:

struct _zend_module_entry {
    ...
    unsigned char zend_debug;    //标识是否为调试版本
    const char *name;    //模块名称
    const struct _zend_function_entry *functions;    //zend 函数块的指针
    int (*module_startup_func)(INIT_FUNC_ARGS);    //模块启动函数
    int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);    //模块关闭函数
    int (*request_startup_func)(INIT_FUNC_ARGS);    //请求启动函数
    int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);    //请求关闭函数
    void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);    //模块信息函数,当脚本调用 phpinfo() 函数时,就会调用这个函数
    const char *version;    //模块版本号
    ...
};

八、实现 get_module() 函数

#ifdef COMPILE_DL_JSON
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(json)                                                                                                                
#endif

        这块基本没啥变化。 get_module() 函数是通过 ZEND_GET_MODULE 宏创建的,定义如下:

#define ZEND_GET_MODULE(name) \                                                                                                      
    BEGIN_EXTERN_C()\
    ZEND_DLEXPORT zend_module_entry *get_module(void) { return &name##_module_entry; }\
    END_EXTERN_C()

        可以看出 ZEND_GET_MODULE 宏展开后就是 get_module() 函数,并返回一个 zend_module_entry 指针,指向的也就是上面提到的保存扩展所有信息的 zend_module_entry 结构体。所以 php 内核就是通过 get_module() 函数与扩展进行通信的。另外,当扩展被编译成一个内建的模块时, get_module() 函数将不会被实现。

        以上就是 php 扩展的大概架构。欢迎交流。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值