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

时间:2022-01-07 22:06:13

内容均以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 ;
}

下次看到 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 ;
}

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, 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, , ); #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 + , PHP_ATTR_FMT_OFFSET + );
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 + , PHP_ATTR_FMT_OFFSET + );
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 + , PHP_ATTR_FMT_OFFSET + );
#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, , );

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, , NULL, required_num_args, , return_reference, , },
#define ZEND_BEGIN_ARG_INFO(name, _unused) \
ZEND_BEGIN_ARG_INFO_EX(name, , ZEND_RETURN_VALUE, -)
#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(, "Color Support", "enabled"); // 表格头,第一个参数是列数,后面是可变参数,第一个参数是几后面就是几个
php_info_print_table_row(2, "Color Support", "enabled");
php_info_print_table_row(, "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) \
{ , modifiable, name, sizeof(name), on_modify, arg1, arg2, arg3, default_value, sizeof(default_value)-, NULL, , , , displayer },

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

static const zend_ini_entry ini_entries[] = {
{ , modifiable, name, sizeof(name), on_modify, arg1, arg2, arg3, default_value, sizeof(default_value)-, NULL, , , , displayer },
{ , , NULL, , NULL, NULL, NULL, NULL, NULL, , NULL, , , , 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, ) : );
} else {
return (ini_entry->value ? strtol(ini_entry->value, NULL, ) : );
}
} return ;
}
/* }}} */

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

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

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

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

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

[php-src]理解Php内核中的函数与INI的更多相关文章

  1. 从极大似然估计的角度理解深度学习中loss函数

    从极大似然估计的角度理解深度学习中loss函数 为了理解这一概念,首先回顾下最大似然估计的概念: 最大似然估计常用于利用已知的样本结果,反推最有可能导致这一结果产生的参数值,往往模型结果已经确定,用于 ...

  2. 理解函数式编程中的函数组合--Monoids&lpar;二&rpar;

    使用函数式语言来建立领域模型--类型组合 理解函数式编程语言中的组合--前言(一) 理解函数式编程中的函数组合--Monoids(二) 继上篇文章引出<范畴论>之后,我准备通过几篇文章,来 ...

  3. 深入理解PHP内核&lpar;十二&rpar;函数-函数的定义、传参及返回值

    原文链接:http://www.orlion.ga/344/ 一.函数的定义 用户函数的定义从function 关键字开始,如下 function foo($var) {    echo $var; ...

  4. Linux内核中kzalloc函数详解

    **************************************************************************************************** ...

  5. 深入理解Solaris内核中互斥锁&lpar;mutex&rpar;与条件变量&lpar;condvar&rpar;之协同工作原理

    在Solaris上写内核模块总是会用到互斥锁(mutex)与条件变量(condvar), 光阴荏苒日月如梭弹指一挥间,Solaris的大船说沉就要沉了,此刻心情不是太好(Orz).每次被年轻的有才华的 ...

  6. 从两个角度理解为什么 JS 中没有函数重载

    函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表(参数个数.类型.顺序)的函数,这组函数被称为重载函数.重载函数通常用来声明一组功能相似的函数,这样做减少了函数名的数量,避免了名字空 ...

  7. armv8&lpar;aarch64&rpar;linux内核中flush&lowbar;dcache&lowbar;all函数详细分析【转】

    转自:http://blog.csdn.net/qianlong4526888/article/details/12062809 版权声明:本文为博主原创文章,未经博主允许不得转载. /* *  __ ...

  8. armv8&lpar;aarch64&rpar;linux内核中flush&lowbar;dcache&lowbar;all函数详细分析

    /* *  __flush_dcache_all() *  Flush the wholeD-cache. * Corrupted registers: x0-x7, x9-x11 */ ENTRY( ...

  9. Linux内核中container&lowbar;of函数详解

    http://www.linuxidc.com/Linux/2016-08/134481.htm

随机推荐

  1. Android&period;mk 基本应用

    如果是在android源码里面编译我们自己的应用,就需要这个android.mk文件,这个文件就告诉android系统应用如何来编译这个应用以及这个应用它所依赖哪些文件等等信息.我对android.m ...

  2. &lbrack;已解决&rsqb; 点击 【Show in system explorer】Eclipse卡死,未响应

    新版的Eclipse自带了 [Show in system explorer] 功能很方便,有一天突然不好用了,点它Eclipse就卡死, 可能由以下原因导致的: (可能性最大)windows本身有问 ...

  3. asp&period;net写验证码

    生成验证码与匹配验证码的服务端代码 <%@ WebHandler Language="C#" Class="ValidataeCodeHandler" % ...

  4. css考核点整理(四)-css盒模型

    http://paranimage.com/css-box-model/

  5. ZABBIX作集中式NGINX性能监控的注意要点

    今天测试好了.但有几个要点要注意. 一是出了问题看日志. 二是主动测试脚本. 三是注意LLD自动发现的ZABBIX类型(TRAPPER,捕捉器类型) 四是有主机上有问题时可以看到(UNSUPPORT) ...

  6. php php打乱数组二维数组、多维数组

    php中的shuffle函数只能打乱一维数组,有什么办法快速便捷的打乱多维数组?手册上提供了 <?php function shuffle_assoc($list) {      if (!is ...

  7. GCD code block

    在这里积累一些片段,由于备忘录. + (DRClass *)sharedDR{ //创建小黑.正常ap模式仅仅有一个小黑,so static DRClass *aDR = nil; static di ...

  8. Cannot convert 0 of type class java&period;lang&period;Integer to class java&period;lang&period;Boolean

    org.apache.catalina.core.ApplicationDispatcher invoke SEVERE: Servlet.service() for servlet jsp thre ...

  9. android ActivityGroup接收不到onActivityResult

    android 框架嵌套,用viewgroup是很好用的.首先实现一个框架的activity,可以继承ActivityGroup 将需要切换的界面,放到ViewGroup里面. 切换如下: Inten ...

  10. python的学习之路day2

    1.什么是常量: 常量在程序中是不变的量 但是在python中所有的变量都可以改 注意:为了防止区分错误,所以python中常量使用大写命名 例如: MYSQL_CONNECTION = '192.1 ...