[php-src] 理解Php内核中的函数与INI

通过llama.cpp与羊驼聊天的网页界面- 详解 Serge 的启动使用

 

内容均以php-5.6.14为例.

 

一. 函数结构

 

内核中定义一个php函数使用 PHP_FUNCTION 宏 包装,扩展也不例外,该宏在 ./main/php.h:343

有着一系列类似以 PHP 命名的 Zend 宏包装器,它们是:

/* PHP-named Zend macro wrappers */
/* 以PHP命名的Zend宏包装器 */

#define PHP_FN                  ZEND_FN
#define PHP_MN                  ZEND_MN
#define PHP_NAMED_FUNCTION      ZEND_NAMED_FUNCTION
#define PHP_FUNCTION            ZEND_FUNCTION             /* PHP_FUNCTION 就是 ZEND_FUNCTION */
#define PHP_METHOD              ZEND_METHOD

#define PHP_RAW_NAMED_FE ZEND_RAW_NAMED_FE
#define PHP_NAMED_FE    ZEND_NAMED_FE
#define PHP_FE          ZEND_FE
#define PHP_DEP_FE      ZEND_DEP_FE
#define PHP_FALIAS      ZEND_FALIAS
#define PHP_DEP_FALIAS  ZEND_DEP_FALIAS
#define PHP_ME          ZEND_ME
#define PHP_MALIAS      ZEND_MALIAS
#define PHP_ABSTRACT_ME ZEND_ABSTRACT_ME
#define PHP_ME_MAPPING  ZEND_ME_MAPPING
#define PHP_FE_END      ZEND_FE_END

#define PHP_MODULE_STARTUP_N    ZEND_MODULE_STARTUP_N
#define PHP_MODULE_SHUTDOWN_N   ZEND_MODULE_SHUTDOWN_N
#define PHP_MODULE_ACTIVATE_N   ZEND_MODULE_ACTIVATE_N
#define PHP_MODULE_DEACTIVATE_N ZEND_MODULE_DEACTIVATE_N
#define PHP_MODULE_INFO_N       ZEND_MODULE_INFO_N

#define PHP_MODULE_STARTUP_D    ZEND_MODULE_STARTUP_D
#define PHP_MODULE_SHUTDOWN_D   ZEND_MODULE_SHUTDOWN_D
#define PHP_MODULE_ACTIVATE_D   ZEND_MODULE_ACTIVATE_D
#define PHP_MODULE_DEACTIVATE_D ZEND_MODULE_DEACTIVATE_D
#define PHP_MODULE_INFO_D       ZEND_MODULE_INFO_D

/* Compatibility macros */
/* 兼容性宏 */ 
#define PHP_MINIT       ZEND_MODULE_STARTUP_N
#define PHP_MSHUTDOWN   ZEND_MODULE_SHUTDOWN_N
#define PHP_RINIT       ZEND_MODULE_ACTIVATE_N
#define PHP_RSHUTDOWN   ZEND_MODULE_DEACTIVATE_N
#define PHP_MINFO       ZEND_MODULE_INFO_N
#define PHP_GINIT       ZEND_GINIT
#define PHP_GSHUTDOWN   ZEND_GSHUTDOWN

#define PHP_MINIT_FUNCTION      ZEND_MODULE_STARTUP_D      /* 可用来定义模块初始时执行一些操作 */
#define PHP_MSHUTDOWN_FUNCTION  ZEND_MODULE_SHUTDOWN_D      /* 可用来定义模块卸载时执行一些操作 */
#define PHP_RINIT_FUNCTION      ZEND_MODULE_ACTIVATE_D      /* 可用来定义请求初始化时执行一些操作 */
#define PHP_RSHUTDOWN_FUNCTION  ZEND_MODULE_DEACTIVATE_D     /* 可用来定义请求结束时执行一些操作 */
#define PHP_MINFO_FUNCTION      ZEND_MODULE_INFO_D        /* 用来定义模块的信息,如phpinfo中的 */
#define PHP_GINIT_FUNCTION      ZEND_GINIT_FUNCTION        /* 用来初始化全局变量 */
#define PHP_GSHUTDOWN_FUNCTION  ZEND_GSHUTDOWN_FUNCTION

#define PHP_MODULE_GLOBALS      ZEND_MODULE_GLOBALS

 

 ZEND_FUNCTION 在 ./Zend/zend_API.h:68

#define ZEND_FN(name) zif_##name
#define ZEND_MN(name) zim_##name
#define ZEND_NAMED_FUNCTION(name)       void name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FUNCTION(name)             ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_METHOD(classname, name)    ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name))

 

函数参数 INTERNAL_FUNCTION_PARAMETERS 在 ./Zend/zend.h:290

#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC
#define INTERNAL_FUNCTION_PARAM_PASSTHRU ht, return_value, return_value_ptr, this_ptr, return_value_used TSRMLS_CC

 

也就是说,使用 PHP_FUNCTION(abc) 定义一个abc函数,预处理结果是 zif_abc(INTERNAL_FUNCTION_PARAMETERS),下面做个试验:

/* 1.c */
#include <stdio.h>

#define ZEND_FN(name) zif_##name
#define ZEND_MN(name) zim_##name
#define ZEND_NAMED_FUNCTION(name)       void name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FUNCTION(name)             ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_METHOD(classname, name)    ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name))

#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

int main()
{
    ZEND_FUNCTION(abc); 

    return 0;
}

下次看到 ZEND_FN() 就可以想到是 zend function name,看到 ZEND_MN() 就能想到 zend method name.

 

使用 `gcc -E -o 1.i 1.c` 生成预处理文件 1.i ,`tail 1.i` 看文件最后的结果,内核中一个函数的形式如下:

int main()
{
    void zif_abc(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC);

    return 0;
}

 

PHP_MINIT_FUNCTION 和 ZEND_MINIT_FUNCTION 的效果是一样的,看下面:

./Zend/zend_API.h:126, 748

....
/* Name macros */
#define ZEND_MODULE_STARTUP_N(module)       zm_startup_##module
#define ZEND_MODULE_SHUTDOWN_N(module)      zm_shutdown_##module
#define ZEND_MODULE_ACTIVATE_N(module)      zm_activate_##module
#define ZEND_MODULE_DEACTIVATE_N(module)    zm_deactivate_##module
#define ZEND_MODULE_POST_ZEND_DEACTIVATE_N(module)  zm_post_zend_deactivate_##module
#define ZEND_MODULE_INFO_N(module)          zm_info_##module
#define ZEND_MODULE_GLOBALS_CTOR_N(module)  zm_globals_ctor_##module
#define ZEND_MODULE_GLOBALS_DTOR_N(module)  zm_globals_dtor_##module

/* Declaration macros */
#define ZEND_MODULE_STARTUP_D(module)       int ZEND_MODULE_STARTUP_N(module)(INIT_FUNC_ARGS)
#define ZEND_MODULE_SHUTDOWN_D(module)      int ZEND_MODULE_SHUTDOWN_N(module)(SHUTDOWN_FUNC_ARGS)
#define ZEND_MODULE_ACTIVATE_D(module)      int ZEND_MODULE_ACTIVATE_N(module)(INIT_FUNC_ARGS)
#define ZEND_MODULE_DEACTIVATE_D(module)    int ZEND_MODULE_DEACTIVATE_N(module)(SHUTDOWN_FUNC_ARGS)
#define ZEND_MODULE_POST_ZEND_DEACTIVATE_D(module)  int ZEND_MODULE_POST_ZEND_DEACTIVATE_N(module)(void)
#define ZEND_MODULE_INFO_D(module)          void ZEND_MODULE_INFO_N(module)(ZEND_MODULE_INFO_FUNC_ARGS)
#define ZEND_MODULE_GLOBALS_CTOR_D(module)  void ZEND_MODULE_GLOBALS_CTOR_N(module)(zend_##module##_globals *module##_globals TSRMLS_DC)
#define ZEND_MODULE_GLOBALS_DTOR_D(module)  void ZEND_MODULE_GLOBALS_DTOR_N(module)(zend_##module##_globals *module##_globals TSRMLS_DC)
....
....
/* For compatibility */
#define ZEND_MINIT          ZEND_MODULE_STARTUP_N
#define ZEND_MSHUTDOWN      ZEND_MODULE_SHUTDOWN_N
#define ZEND_RINIT          ZEND_MODULE_ACTIVATE_N
#define ZEND_RSHUTDOWN      ZEND_MODULE_DEACTIVATE_N
#define ZEND_MINFO          ZEND_MODULE_INFO_N
#define ZEND_GINIT(module)      ((void (*)(void* TSRMLS_DC))(ZEND_MODULE_GLOBALS_CTOR_N(module)))
#define ZEND_GSHUTDOWN(module)  ((void (*)(void* TSRMLS_DC))(ZEND_MODULE_GLOBALS_DTOR_N(module)))

#define ZEND_MINIT_FUNCTION         ZEND_MODULE_STARTUP_D
#define ZEND_MSHUTDOWN_FUNCTION     ZEND_MODULE_SHUTDOWN_D
#define ZEND_RINIT_FUNCTION         ZEND_MODULE_ACTIVATE_D
#define ZEND_RSHUTDOWN_FUNCTION     ZEND_MODULE_DEACTIVATE_D
#define ZEND_MINFO_FUNCTION         ZEND_MODULE_INFO_D
#define ZEND_GINIT_FUNCTION         ZEND_MODULE_GLOBALS_CTOR_D
#define ZEND_GSHUTDOWN_FUNCTION     ZEND_MODULE_GLOBALS_DTOR_D
....

所以如果我们要定义自己的启动函数原型,可以定义 ZEND_MINIT_FUNCTION 的兼容性宏,多文件时传入其它模块。

 

#define MY_STARTUP_FUNCTION(module)    ZEND_MINIT_FUNCTION(my_##module) 

这个 MY_STARTUP_FUNCTION 作用和 ZEND_MINIT_FUNCTION 一样,在这里是用在多文件中定义启动其它主文件模块的函数。

或者,直接定义成最终形式:

#define MY_STARTUP_FUNCTION(module)    ZEND_MODULE_STARTUP_N(my_##module)(INIT_FUNC_ARGS)

这两种都是原型的定义。

 

自定义 启动/卸载 函数调用的宏:

#define MY_STARTUP(module)    ZEND_MODULE_STARTUP_N(my_##module)(INIT_FUNC_ARGS_PASSTHRU)

#define MY_SHUTDOWN(module)    ZEND_MODULE_SHUTDOWN_N(my_##module)(INIT_FUNC_ARGS_PASSTHRU)

这个 MY_STARTUP 是用在主模块的初始化函数中启动附加组件的。从参数可以看出,它是直接调用。

或者,采用高层宏的形式:

#define MY_STARTUP(module)    ZEND_MINIT(my_##module)(INIT_FUNC_ARGS_PASSTHRU) 

 

模块操作的参数能说明一些问题:

./Zend/zend_modules.h:30

....
#define INIT_FUNC_ARGS      int type, int module_number TSRMLS_DC
#define INIT_FUNC_ARGS_PASSTHRU type, module_number TSRMLS_CC
#define SHUTDOWN_FUNC_ARGS  int type, int module_number TSRMLS_DC
#define SHUTDOWN_FUNC_ARGS_PASSTHRU type, module_number TSRMLS_CC
#define ZEND_MODULE_INFO_FUNC_ARGS zend_module_entry *zend_module TSRMLS_DC
#define ZEND_MODULE_INFO_FUNC_ARGS_PASSTHRU zend_module TSRMLS_CC
....

 

 

上面 zif_abc 函数参数中还有一个看起来奇怪的东西 TSRMLS_DC,线程安全资源管理的宏;

./TSRM/TSRM.h:166

#ifdef ZTS

..........

#define TSRMLS_FETCH()          void ***tsrm_ls = (void ***) ts_resource_ex(0, NULL)
#define TSRMLS_FETCH_FROM_CTX(ctx)  void ***tsrm_ls = (void ***) ctx
#define TSRMLS_SET_CTX(ctx)     ctx = (void ***) tsrm_ls
#define TSRMG(id, type, element)    (((type) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(id)])->element)
#define TSRMLS_D    void ***tsrm_ls
#define TSRMLS_DC   , TSRMLS_D
#define TSRMLS_C    tsrm_ls
#define TSRMLS_CC   , TSRMLS_C

#else    /* non ZTS */

#define TSRMLS_FETCH()
#define TSRMLS_FETCH_FROM_CTX(ctx)
#define TSRMLS_SET_CTX(ctx)
#define TSRMLS_D    void
#define TSRMLS_DC
#define TSRMLS_C
#define TSRMLS_CC

#endif    /* ZTS */

TSRMLS_* 系列总共有四个,其展开意思是 thread safe resource manager local stroage;

在不启用zend线程安全的时候,它们并没有内容可以替换,ZTS 就是 zend thread safe;

 

TSRMLS_DC 和 TSRMLS_CC 对应比 TSRMLS_D 和 TSRMLS_C 多加了一个逗号,

TSRMLS_DC 就是 , TSRMLS_D,也就是 , void ***tsrm_ls ;它在函数定义中使用,D有define的意思.

TSRMLS_CC就是 , TSRMLS_C,也就是 , tsrm_ls ;它在函数调用中使用,C有call的意思.

这里有篇关于TSRM的靠谱文章:揭秘TSRM(Introspecting TSRM)

 

二. 参数

 

int ht,            用户实际传入的参数数目,ZEND_NUM_ARGS() 获取.

zval *return_value,      指向由return_value填充的PHP变量的指针,(所指向的变量值)返回给函数调用者,默认类型是 IS_NULL.

zval **return_value_ptr,  当返回PHP的引用,设置为指向变量的指针.(需要引用形式的返回值时使用)

zval *this_ptr,         如果此函数是类的方法调用,相当于 $this 对象.

int return_value_used,     用户调用此函数有没有用到函数返回值,用到就是1.

 

在 [php-src]窥探Php内核中的变量 中讲到了设置变量的值,ZVAL_*() 系列函数,第一个参数就是 return_value,如果我们不用 RETURN_*() 系列函数主动返回某个值的话,同样可以用 ZVAL_*() ,效果是一样的。

long t = 10;
RETURN_LONG(t); // 同 RETVAL_LONG(t);return; 也相当于 ZVAL_LONG(return_value, t);return;

 

内核中修改传递的参数:

{
    zval *arg;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arg) == FAILURE) {
        RETURN_NULL();
    }   

    // PHP5.4 参数不再支持引用方式传递,所以不需要判断 arg->is_ref__gc
    convert_to_string(arg);
    ZVAL_STRING(arg, "abc", 1); 

    return;
}
<?php
$a = "AAA";
test($a);
// allow_call_time_pass_reference 已从5.4中移除,所以下面的用法是非法的,只能在函数定义时使用,如:function test(&$var)
// test(&$a); // PHP Fatal error:  Call-time pass-by-reference has been removed
// 参考:http://php.net/manual/zh/ini.core.php
echo $a; // abc

知道了上面的原理,我们直接对参数值进行修改,别忘了函数原型中提醒用户会更改原值。

 

三. zend_API.h 中的宏函数

 

array_init() 在 ./Zend/zend_API.h:363

#define array_init(arg)         _array_init((arg), 0 ZEND_FILE_LINE_CC)
#define array_init_size(arg, size) _array_init((arg), (size) ZEND_FILE_LINE_CC)
#define object_init(arg)        _object_init((arg) ZEND_FILE_LINE_CC TSRMLS_CC)
#define object_init_ex(arg, ce) _object_init_ex((arg), (ce) ZEND_FILE_LINE_CC TSRMLS_CC)
#define object_and_properties_init(arg, ce, properties) _object_and_properties_init((arg),  (ce), (properties) ZEND_FILE_LINE_CC TSRMLS_CC)

ZEND_API int _array_init(zval *arg, uint size ZEND_FILE_LINE_DC);

 

_array_init() 的实现在 ./Zend/zend_API.c:1009

/* Argument parsing API -- andrei */
ZEND_API int _array_init(zval *arg, uint size ZEND_FILE_LINE_DC) /* {{{ */
{
    ALLOC_HASHTABLE_REL(Z_ARRVAL_P(arg));

    _zend_hash_init(Z_ARRVAL_P(arg), size, ZVAL_PTR_DTOR, 0 ZEND_FILE_LINE_RELAY_CC);
    Z_TYPE_P(arg) = IS_ARRAY;
    return SUCCESS;
}
/* }}} */

 

ZEND_FILE_LINE_DC 在 zend.h 第217行:

#if ZEND_DEBUG
#define ZEND_FILE_LINE_D                const char *__zend_filename, const uint __zend_lineno
#define ZEND_FILE_LINE_DC               , ZEND_FILE_LINE_D
#define ZEND_FILE_LINE_ORIG_D           const char *__zend_orig_filename, const uint __zend_orig_lineno
#define ZEND_FILE_LINE_ORIG_DC          , ZEND_FILE_LINE_ORIG_D
#define ZEND_FILE_LINE_RELAY_C          __zend_filename, __zend_lineno
#define ZEND_FILE_LINE_RELAY_CC         , ZEND_FILE_LINE_RELAY_C
#define ZEND_FILE_LINE_C                __FILE__, __LINE__
#define ZEND_FILE_LINE_CC               , ZEND_FILE_LINE_C
#define ZEND_FILE_LINE_EMPTY_C          NULL, 0
#define ZEND_FILE_LINE_EMPTY_CC         , ZEND_FILE_LINE_EMPTY_C
#define ZEND_FILE_LINE_ORIG_RELAY_C     __zend_orig_filename, __zend_orig_lineno
#define ZEND_FILE_LINE_ORIG_RELAY_CC    , ZEND_FILE_LINE_ORIG_RELAY_C
#define ZEND_ASSERT(c)                  assert(c)
#else
#define ZEND_FILE_LINE_D
#define ZEND_FILE_LINE_DC
#define ZEND_FILE_LINE_ORIG_D
#define ZEND_FILE_LINE_ORIG_DC
#define ZEND_FILE_LINE_RELAY_C
#define ZEND_FILE_LINE_RELAY_CC
#define ZEND_FILE_LINE_C
#define ZEND_FILE_LINE_CC
#define ZEND_FILE_LINE_EMPTY_C
#define ZEND_FILE_LINE_EMPTY_CC
#define ZEND_FILE_LINE_ORIG_RELAY_C
#define ZEND_FILE_LINE_ORIG_RELAY_CC
#define ZEND_ASSERT(c)
#endif  /* ZEND_DEBUG */

上面 __file_name 就是函数一个形参名,没特殊含义,用两个下划线有非常局部(私有)的感觉。

 

API函数的存在,能让我们更方便在内核中完成某项功能。

看到这里,你或许对函数原型和函数前面的 ZEND_API 感到困惑,这是什么,定义它们的文件有:

./configure.in:22

./Zend/configure.in:16

./main/php_config.h.in:8

./main/php_config.h:6

./Zend/zend_config.win32.h:78

 

configure.in 和 ./Zend/configure.in 由 autoconf 处理,并生成配置文件,它们都含下面这段:

AH_TOP([
#if defined(__GNUC__) && __GNUC__ >= 4
# define ZEND_API __attribute__ ((visibility("default")))
# define ZEND_DLEXPORT __attribute__ ((visibility("default")))
#else
# define ZEND_API
# define ZEND_DLEXPORT
#endif

#define ZEND_DLIMPORT

#undef uint
#undef ulong

/* Define if you want to enable memory limit support */
#define MEMORY_LIMIT 0
])

__attribute__ ((visibility("default"))) 用于设置可见性,default使符号在所有情况下都被输出. 参考:控制符的可见性

ps:

  __GNUC__ 是gcc编译器编译代码时预定义的一个宏。需要针对gcc编写代码时, 可以使用该宏进行条件编译。

  __GNUC__ 的值表示gcc的版本。需要针对gcc特定版本编写代码时,也可以使用该宏进行条件编译。

  __GNUC__ 的类型是“int”,该宏被扩展后, 得到的是整数字面值。可以通过仅预处理,查看宏扩展后的文本。

 

./Zend/zend_config.win32.h

#ifdef LIBZEND_EXPORTS
#   define ZEND_API __declspec(dllexport)
#else
#   define ZEND_API __declspec(dllimport)
#endif

 

./configure.in  ->生成  ./main/php_config.h.in  ->生成  ./main/php_config.h

 

和 ZEND_API 效果一样的有 PHPAPI,

./main/php.h:60:# define PHPAPI __attribute__ ((visibility("default")))

 

四. 错误输出及运行时信息

 

 1). 错误输出函数

 
定义php错误级别的宏 ./Zend/zend_errors.h
#ifndef ZEND_ERRORS_H
#define ZEND_ERRORS_H

#define E_ERROR             (1<<0L)
#define E_WARNING           (1<<1L)
#define E_PARSE             (1<<2L)
#define E_NOTICE            (1<<3L)
#define E_CORE_ERROR        (1<<4L)
#define E_CORE_WARNING      (1<<5L)
#define E_COMPILE_ERROR     (1<<6L)
#define E_COMPILE_WARNING   (1<<7L)
#define E_USER_ERROR        (1<<8L)
#define E_USER_WARNING      (1<<9L)
#define E_USER_NOTICE       (1<<10L)
#define E_STRICT            (1<<11L)
#define E_RECOVERABLE_ERROR (1<<12L)
#define E_DEPRECATED        (1<<13L)
#define E_USER_DEPRECATED   (1<<14L)

#define E_ALL (E_ERROR | E_WARNING | E_PARSE | E_NOTICE | E_CORE_ERROR | E_CORE_WARNING | 
E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE |
E_RECOVERABLE_ERROR | E_DEPRECATED | E_USER_DEPRECATED | E_STRICT) #define E_CORE (E_CORE_ERROR | E_CORE_WARNING) #endif /* ZEND_ERRORS_H */

 

定义错误输出函数的宏 ./main/php.h:292

#define php_error zend_error
#define error_handling_t zend_error_handling_t

BEGIN_EXTERN_C()
static inline ZEND_ATTRIBUTE_DEPRECATED void php_set_error_handling(error_handling_t error_handling, zend_class_entry *exception_class TSRMLS_DC)
{
    zend_replace_error_handling(error_handling, exception_class, NULL TSRMLS_CC);
}
static inline ZEND_ATTRIBUTE_DEPRECATED void php_std_error_handling() {}

PHPAPI void php_verror(const char *docref, const char *params, int type, const char *format, va_list args TSRMLS_DC) PHP_ATTRIBUTE_FORMAT(printf, 4, 0);

#ifdef ZTS
#define PHP_ATTR_FMT_OFFSET 1
#else
#define PHP_ATTR_FMT_OFFSET 0
#endif

/* PHPAPI void php_error(int type, const char *format, ...); */
PHPAPI void php_error_docref0(const char *docref TSRMLS_DC, int type, const char *format, ...)
    PHP_ATTRIBUTE_FORMAT(printf, PHP_ATTR_FMT_OFFSET + 3, PHP_ATTR_FMT_OFFSET + 4); 
PHPAPI void php_error_docref1(const char *docref TSRMLS_DC, const char *param1, int type, const      char *format, ...)
    PHP_ATTRIBUTE_FORMAT(printf, PHP_ATTR_FMT_OFFSET + 4, PHP_ATTR_FMT_OFFSET + 5);
PHPAPI void php_error_docref2(const char *docref TSRMLS_DC, const char *param1, const char *param2,  int type, const char *format, ...)
    PHP_ATTRIBUTE_FORMAT(printf, PHP_ATTR_FMT_OFFSET + 5, PHP_ATTR_FMT_OFFSET + 6);
#ifdef PHP_WIN32
PHPAPI void php_win32_docref2_from_error(DWORD error, const char *param1, const char *param2         TSRMLS_DC);
#endif
END_EXTERN_C()

#define php_error_docref php_error_docref0

内核中很多地方出现 BEGIN_EXTERN_C() 和 END_EXTERN_C() 将程序包起来的情况,它们实际上是下面这种东西,你一看便知:

#ifdef __cplusplus
#define BEGIN_EXTERN_C() extern "C" {
#define END_EXTERN_C() }
#else
#define BEGIN_EXTERN_C()
#define END_EXTERN_C()
#endif

如果定义了__cplusplus,就把C程序用 extern "C" { } 包起来当C++处理,用于指定编译和链接规则,实现C和C++的混合编程. 参考:C++项目中的extern "C" {}

 

zend_error 原型在 ./Zend/zend.h:711,实现在 ./Zend/zend.c:1031

ZEND_API void zend_error(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);

 

php_error_docref0 实现在 ./main.c:947

/* {{{ php_error_docref0 */
/* See: CODING_STANDARDS for details. */
PHPAPI void php_error_docref0(const char *docref TSRMLS_DC, int type, const char *format, ...) 
{
    va_list args;

    va_start(args, format);
    php_verror(docref, "", type, format, args TSRMLS_CC);
    va_end(args);
}
/* }}} */

 

php_error 即 zend_error,php_error_docref 即 php_error_docref0

php_error 与 php_error_docref 的使用基本相同,参数位置不一样、效果略有差异;

由于 php_error 只是 zend_error 的别名,我们应该尽量使用 zend_error.

 

如:php_error_docref(NULL TSRMLS_CC, E_WARNING, "has been disabled for security reasons");

将输出:Warning: testing(): has been disabled for security reasons in /home/www/1.php on line 3

 

如:zend_error(E_WARNING, "has been disabled for security reasons");

将输出:Warning: has been disabled for security reasons in /home/www/1.php on line 3

 

 2). 获得运行时函数信息的函数

 

get_active_function_name(TSRMLS_C)    查看当前执行的函数名.
zend_get_executed_filename(TSRMLS_C)  查看当前执行的文件名.
zend_get_executed_lineno(TSRMLS_C)    查看当前执行到哪一行代码.

 

./Zend/zend_execute.h:353

ZEND_API const char *get_active_function_name(TSRMLS_D);

./Zend/zend_execute_API.c:366

ZEND_API const char *get_active_function_name(TSRMLS_D) /* {{{ */
{
    if (!zend_is_executing(TSRMLS_C)) {
        return NULL;
    }    
    switch (EG(current_execute_data)->function_state.function->type) {
        case ZEND_USER_FUNCTION: {
                const char *function_name = ((zend_op_array *) EG(current_execute_data)-             >function_state.function)->function_name;

                if (function_name) {
                    return function_name;
                } else {
                    return "main";
                }
            }
            break;
        case ZEND_INTERNAL_FUNCTION:
            return ((zend_internal_function *) EG(current_execute_data)->function_state.function)-   >function_name;
            break;
        default:
            return NULL;
    }    
}
/* }}} */

 

 3). 示例

zend_error( E_WARNING, 
  "%s() has been disabled for security reasons; current file is %s; current line is %i",
  get_active_function_name(TSRMLS_C),
  zend_get_executed_filename(TSRMLS_C),
  zend_get_executed_lineno(TSRMLS_C) );

zend_error(E_WARNING, "this is a warning message\n");

 

给内核传递函数参数信息的宏:(用 zend_parse_parameters 解析参数时,可以不使用 arginfo 指定参数类型 )

./Zend/zend_API.h:108

#define ZEND_ARG_INFO(pass_by_ref, name)       { #name, sizeof(#name)-1, NULL, 0, 0,   pass_by_ref, 0, 0 },
....

#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)  \
    static const zend_arg_info name[] = {                                                                       \
        { NULL, 0, NULL, required_num_args, 0, return_reference, 0, 0 },
#define ZEND_BEGIN_ARG_INFO(name, _unused)  \
    ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO()     };

 

【写一个color扩展的welcome函数】

定义函数并添加为可用:

/* {{{ arginfo */
ZEND_BEGIN_ARG_INFO_EX(arginfo_welcome, 0, 0, 0)
    ZEND_ARG_INFO(0, arg)
ZEND_END_ARG_INFO()
/* }}} */

/* {{{ 自定义welcome */
PHP_FUNCTION(welcome)
{
    php_printf("It works, Welcome!\n");
}
/* }}} */

/* {{{ color_functions[]
 *  
 * Every user visible function must have an entry in color_functions[]. 可用的函数都加到里面
 */
const zend_function_entry color_functions[] = {
    PHP_FE(confirm_color_compiled,  NULL)       /* For testing, remove later. */
    PHP_FE(welcome, arginfo_welcome)
    PHP_FALIAS(wel, welcome, arginfo_welcome)  /* alias */
    PHP_FE_END  /* Must be the last line in color_functions[] */
};  
/* }}} */

 

make && sudo make install

运行 php5.6.14 -r 'welcome();' 就会输出:It works, Welcome!

注意:如果你本机装了多个php,一不小心打成了 php -r 'welcome();',那么你得到的永远都是undefined function

 

为扩展增加phpinfo信息:

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(color)
{
    php_info_print_table_start(); // 开始表格, 无参数
    // php_info_print_table_header(2, "Color Support", "enabled"); // 表格头,第一个参数是列数,后面是可变参数,第一个参数是几后面就是几个
php_info_print_table_row(2, "Color Support", "enabled");
php_info_print_table_row(2, "author", "weiChen");       // 表格行,与header头仅颜色不一样,同样第一个参数是列数,后面是列的内容
php_info_print_table_row(2, "dependencies", "No"); php_info_print_table_end(); // 结束表格, 无参数 DISPLAY_INI_ENTRIES(); // 开启此行向 phpinfo 中显示INI配置 } /* }}} */

注意模块信息的修改除了重新编译安装,还需要重启Php使之生效.

  

增加可供查看的 INI 配置项:

  1). 模块初始阶段引入 结束阶段去除

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(color)
{
    REGISTER_INI_ENTRIES();
    return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(color)
{
    UNREGISTER_INI_ENTRIES();
    return SUCCESS;
}
/* }}} */

 

  2). 具体配置项

static PHP_INI_MH(onChangeTest)  // 定义回调函数onChangeTest
{
    php_printf("ini entry has changed to %s\n", new_value);
    return SUCCESS;
}

/* {{{ PHP_INI
 */
PHP_INI_BEGIN() // 开始


// 1.配置项名称, 2.初始值, 3.权限(PHP_INI_PERDIR、PHP_INI_SYSTEM、PHP_INI_USER、PHP_INI_ALL), 4.改变这个值时的回调函数
// PHP_INI_PERDIR | PHP_INI_SYSTEM 指令只允许在php.ini或httpd.conf等配置中修改,PHP_INI_USER 只能在用户脚本中修改 如ini_set().
// 具体配置项 PHP_INI_ENTRY("color.test", "very good", PHP_INI_ALL, onChangeTest)
//STD_PHP_INI_ENTRY("color.global_value", "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_color_globals, color_globals) //STD_PHP_INI_ENTRY("color.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_color_globals, color_globals)

PHP_INI_END() // 结束 /* }}} */

宏的转换关系都在 ./Zend/zend_ini.h 中:

PHP_INI_ENTRY -> ZEND_INI_ENTRY -> ZEND_INI_ENTRY_EX -> ZEND_INI_ENTRY3_EX

STD_PHP_INI_ENTRY -> STD_ZEND_INI_ENTRY -> ZEND_INI_ENTRY2 -> ZEND_INI_ENTRY2_EX -> ZEND_INI_ENTRY3_EX

 

由于 ZEND_INI_ENTRY 只需要传4个参数,到最后变成 ZEND_INI_ENTRY3_EX 时后面3个参数已经默认为 NULL .

当不需要后面的参数时用 PHP_INI_ENTRY,想传参的时候用 STD_PHP_INI_ENTRY,最后两个参数是由下面这个扩展内的函数初始化的.

/* {{{ php_color_init_globals
 */
/* Uncomment this function if you have INI entries
static void php_color_init_globals(zend_color_globals *color_globals)
{
    color_globals->global_value = 0;
    color_globals->global_string = NULL;
}
*/
/* }}} */

 

./Zend/zend_ini.h:114

#define ZEND_INI_BEGIN()        static const zend_ini_entry ini_entries[] = {
#define ZEND_INI_END()      { 0, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0, NULL } };
#define ZEND_INI_ENTRY3_EX(name, default_value, modifiable, on_modify, arg1, arg2, arg3, displayer) \
    { 0, modifiable, name, sizeof(name), on_modify, arg1, arg2, arg3, default_value, sizeof(default_value)-1, NULL, 0, 0, 0, displayer },

最终的 PHP_INI 区块实际编译成一个如下结构的数组:

static const zend_ini_entry ini_entries[] = {
    { 0, modifiable, name, sizeof(name), on_modify, arg1, arg2, arg3, default_value, sizeof(default_value)-1, NULL, 0, 0, 0, displayer },
    { 0, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0, NULL }
}

 

  3). 访问配置项的宏 ( Zend/zend_ini.h:157 )

#define INI_INT(name) zend_ini_long((name), sizeof(name), 0)
#define INI_FLT(name) zend_ini_double((name), sizeof(name), 0)
#define INI_STR(name) zend_ini_string_ex((name), sizeof(name), 0, NULL)
#define INI_BOOL(name) ((zend_bool) INI_INT(name))

#define INI_ORIG_INT(name)  zend_ini_long((name), sizeof(name), 1)
#define INI_ORIG_FLT(name)  zend_ini_double((name), sizeof(name), 1)
#define INI_ORIG_STR(name)  zend_ini_string((name), sizeof(name), 1)
#define INI_ORIG_BOOL(name) ((zend_bool) INI_ORIG_INT(name))

头四个用来获取INI设置的当前值,后四个用来读取未经修改的INI值 ( Zend/zend_ini.h:102 ).

ZEND_API long zend_ini_long(char *name, uint name_length, int orig);
ZEND_API double zend_ini_double(char *name, uint name_length, int orig);
ZEND_API char *zend_ini_string(char *name, uint name_length, int orig);
ZEND_API char *zend_ini_string_ex(char *name, uint name_length, int orig, zend_bool *exists);

 

实现在 Zend/zend_ini.c:348

/*
 * Data retrieval
 */

ZEND_API long zend_ini_long(char *name, uint name_length, int orig) /* {{{ */
{
    zend_ini_entry *ini_entry;
    TSRMLS_FETCH();  // 在需要访问全局变量的代码块开头使用TSRMLS_FETCH()来提取上下文

    if (zend_hash_find(EG(ini_directives), name, name_length, (void **) &ini_entry) == SUCCESS) {
     if (orig && ini_entry->modified) { return (ini_entry->orig_value ? strtol(ini_entry->orig_value, NULL, 0) : 0); } else { return (ini_entry->value ? strtol(ini_entry->value, NULL, 0) : 0); } } return 0; } /* }}} */

 

编译扩展、重启php生效:make && sudo make install && sudo sh /home/weiChen/reloadPhp.sh

 

Refer:什么是PHP内核函数

开发文档:https://github.com/farwish/php-core-hack

推荐文章:用C/C++扩展你的PHP

Link: http://www.cnblogs.com/farwish/p/5248686.html

posted on 2016-04-16 00:09  ercom  阅读(1914)  评论(0编辑  收藏  举报