内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

PHP7扩展开发(8):访问超级全局变量

2017-08-12 15:26 出处:清屏网 人气: 评论(0

本章节的知识点比较少,但是会更多深入到zend代码里去。

内容涉及:

  • 注册和获取全局常量。
  • 读取全局超级变量,例如:$_GET,$_POST,$_SERVER。

正式开始

在扩展的启动回调中:

int extension_startup(int type, int module_number)

我们注册一个全局常量:

// register constant
zend_constant c;
c.name = zend_string_init("GITHUB", sizeof("GITHUB") - 1, 1); // persistant memory
ZVAL_STR(&c.value, zend_string_init("https://github.com/owenliang/php7-extension-explore",
sizeof("https://github.com/owenliang/php7-extension-explore"), 1)); // persistant memory
c.flags = CONST_CS | CONST_PERSISTENT;
c.module_number = module_number;
assert(zend_register_constant(&c) == SUCCESS);

首先定义一个zend_constant结构,它的定义如下:

typedef struct _zend_constant {
zval value;
zend_string *name;
int flags;
int module_number;
} zend_constant;

注意它不是像zend_string一样存储在zval的底层数据结构,而是一个独立的类型。因为常量在整个PHP执行期间始终存在,所以填充zend_constant的各个字段都使用了持久化内存分配。

name是常量的名称,叫做GITHUB。value是一个zval类型,代表常量的值,这里我分配了一个string类型的zval。flags是常量的属性,它的枚举值包含:

#define CONST_CS(1<<0)/* Case Sensitive */
#define CONST_PERSISTENT(1<<1)/* Persistent */
#define CONST_CT_SUBST(1<<2)/* Allow compile-time substitution */

一般都是使用前两个,CONST_CS代表常量的名字是大小写敏感的,CONST_PERSISTENT表示这个常量持久存在。最后一个CONST_CT_SUBST没有见到使用的,从注释来看像是允许用户PHP代码里覆盖这个常量的值。

最后的module_number是回调函数extension_startup传给我们的模块唯一标识,从而说明这个常量是我们的扩展注册的。

接下来通过zend_register_constant方法注册这个常量,其定义如下:

ZEND_API int zend_register_constant(zend_constant *c)

这样注册之后,在PHP代码里就可以像这样访问它了:

echo GITHUB . PHP_EOL;

那么,在扩展里又该如何访问到这个常量呢?在myext类的成员方法中:

void zim_myext_version(zend_execute_data *execute_data, zval *return_value)

我在末尾添加了一段读取常量的代码:

// read constant
zend_string *cname = zend_string_init("GITHUB", sizeof("GITHUB") - 1, 0);
zval *c_github = zend_get_constant(cname);
assert(Z_TYPE_P(c_github) == IS_STRING);
TRACE("zend_get_constant(GITHUB)=%.*s", Z_STRLEN_P(c_github), Z_STRVAL_P(c_github));
zend_string_release(cname);

首先创建一个zend_string表示常量的名字,然后通过zend_get_constant即可获得其zval,这个zval就是之前注册时zend_constant对象里的那个zval。通过断言确认它是字符串类型,打印其值的确是之前注册的内容,最后不要忘记释放cname的内存。

zend_get_constant的实现如下:

ZEND_API zval *zend_get_constant(zend_string *name)
{
zend_constant *c;
ALLOCA_FLAG(use_heap)

if ((c = zend_hash_find_ptr(EG(zend_constants), name)) == NULL) {
char *lcname = do_alloca(ZSTR_LEN(name) + 1, use_heap);
zend_str_tolower_copy(lcname, ZSTR_VAL(name), ZSTR_LEN(name));
if ((c = zend_hash_str_find_ptr(EG(zend_constants), lcname, ZSTR_LEN(name))) != NULL) {
if (c->flags & CONST_CS) {
c = NULL;
}
} else {
c = zend_get_special_constant(ZSTR_VAL(name), ZSTR_LEN(name));
}
free_alloca(lcname, use_heap);
}

return c ? &c->value : NULL;
}

你会发现,常量最终保存在一个全局的哈希表EG(zend_constants)里。EG这个宏是指executor globals,大概就是指执行时的全局变量,它最终指向一个全局单例对象zend_executor_globals zend_executor_globals,其具体定义如下:

# define EG(v) (executor_globals.v)
extern ZEND_API zend_executor_globals executor_globals;


typedef struct _zend_executor_globals zend_executor_globals;


struct _zend_executor_globals {
zval uninitialized_zval;
zval error_zval;

/* symbol table cache */
zend_array *symtable_cache[SYMTABLE_CACHE_SIZE];
zend_array **symtable_cache_limit;
zend_array **symtable_cache_ptr;

zend_array symbol_table;/* main symbol table */

HashTable included_files; /* files already included */

JMP_BUF *bailout;

int error_reporting;
int exit_status;

HashTable *function_table;/* function symbol table */
HashTable *class_table; /* class table */
HashTable *zend_constants;/* constants table */

zval*vm_stack_top;
zval*vm_stack_end;
zend_vm_stackvm_stack;

struct _zend_execute_data *current_execute_data;

可见除了zend_constants,我们的全局函数应该是保存在function_table中,class应该保存在class_table中,而我们的zif/zim函数的第一个回调参数zend_execute_data其实就是EG里的current_execute_data,大概了解这个关系即可。

另外一个常见的需求,就是获取全局超级变量,例如:$_SERVER,$_GET…。在一个扩展中,又该如何获取呢?我将相关的代码实现在如下函数中:

int extension_before_request(int type, int module_number)

这里我假设要访问$_SERVER,首先调用了:

// try active jit super globals
zend_is_auto_global_str("_SERVER", sizeof("_SERVER") - 1);

它会帮我们确保_SERVER已经被注册到全局符号表,这是什么意思呢?不如从Zend的源码里找出来龙去脉,以便用的明明白白。

void php_startup_auto_globals(void)
{
zend_register_auto_global(zend_string_init("_GET", sizeof("_GET")-1, 1), 0, php_auto_globals_create_get);
zend_register_auto_global(zend_string_init("_POST", sizeof("_POST")-1, 1), 0, php_auto_globals_create_post);
zend_register_auto_global(zend_string_init("_COOKIE", sizeof("_COOKIE")-1, 1), 0, php_auto_globals_create_cookie);
zend_register_auto_global(zend_string_init("_SERVER", sizeof("_SERVER")-1, 1), PG(auto_globals_jit), php_auto_globals_create_server);
zend_register_auto_global(zend_string_init("_ENV", sizeof("_ENV")-1, 1), PG(auto_globals_jit), php_auto_globals_create_env);
zend_register_auto_global(zend_string_init("_REQUEST", sizeof("_REQUEST")-1, 1), PG(auto_globals_jit), php_auto_globals_create_request);
zend_register_auto_global(zend_string_init("_FILES", sizeof("_FILES")-1, 1), 0, php_auto_globals_create_files);
}

PHP在处理一个请求之前,会执行这个函数来注册若干的超级全局变量,比如_GET,_POST。那么zend_register_auto_global做了什么呢?

int zend_register_auto_global(zend_string *name, zend_bool jit, zend_auto_global_callback auto_global_callback) /* {{{ */
{
zend_auto_global auto_global;
int retval;

auto_global.name = zend_new_interned_string(name);
auto_global.auto_global_callback = auto_global_callback;
auto_global.jit = jit;

retval = zend_hash_add_mem(CG(auto_globals), auto_global.name, &auto_global, sizeof(zend_auto_global)) != NULL ? SUCCESS : FAILURE;

zend_string_release(name);
return retval;
}

它将一个zend_auto_global对象保存了CG(auto_globals)这个全局变量里,CG类似于EG,稍后再看一下其定义即可。

先来看一下zend_auto_global的定义:

typedef zend_bool (*zend_auto_global_callback)(zend_string *name);
typedef struct _zend_auto_global {
zend_string *name;
zend_auto_global_callback auto_global_callback;
zend_bool jit;
zend_bool armed;
} zend_auto_global;

name是超级全局变量的名字,auto_global_callback是一个回调函数,未来将用于初始化超级变量的值。jit是JUST IN TIME的意思,即时编译,在调用zend_register_auto_global的时候,有的传了0有的传了1,具体用途稍后再说。armed字段表示该超级全局变量是否完成初始化,但是这里尚且没有赋值,稍后解释。

注册了若干超级全局变量之后,PHP其实紧接着调用了这个函数:

ZEND_API void zend_activate_auto_globals(void) /* {{{ */
{
zend_auto_global *auto_global;

ZEND_HASH_FOREACH_PTR(CG(auto_globals), auto_global) {
if (auto_global->jit) {
auto_global->armed = 1;
} else if (auto_global->auto_global_callback) {
auto_global->armed = auto_global->auto_global_callback(auto_global->name);
} else {
auto_global->armed = 0;
}
} ZEND_HASH_FOREACH_END();
}

之前register方法将所有超级全局变量注册到了CG(auto_globals)这个哈希表里,其含义是compiler globals,结构体片段如下:

# define CG(v) (compiler_globals.v)
extern ZEND_API struct _zend_compiler_globals compiler_globals;

struct _zend_compiler_globals {
zend_stack loop_var_stack;

zend_class_entry *active_class_entry;

zend_string *compiled_filename;

int zend_lineno;

zend_op_array *active_op_array;

HashTable *function_table;/* function symbol table */
HashTable *class_table; /* class table */

HashTable filenames_table;

HashTable *auto_globals;

CG是编译期全局变量,大概是在解释PHP代码为opcode期间保存信息的空间。EG是执行期全局变量,大概就是运行期间保存信息的空间。一个PHP代码要经历编译 -> 解释执行的过程,所以这是我的大概理解。

回到函数:

ZEND_API void zend_activate_auto_globals(void) /* {{{ */

它遍历了CG(auto_globals)中保存的每个超级全局变量,如果它的jit=1,就初始化armed=1,而armed为1表示这个超级全局变量尚未初始化。如果jit=0并且有auto_global_callback回调函数,那么立即调用它完成变量的初始化并将成功与否赋值给armed。

PHP这个设计其实就是利用”懒惰”的思路进行优化,标记了JIT=1的超级全局变量,默认PHP是不会执行auto_global_callback进行初始化的。除非用户主动的声明要访问它,也就是调用zend_is_auto_global_str(zend_is_auto_global):

zend_bool zend_is_auto_global(zend_string *name) /* {{{ */
{
zend_auto_global *auto_global;

if ((auto_global = zend_hash_find_ptr(CG(auto_globals), name)) != NULL) {
if (auto_global->armed) {
auto_global->armed = auto_global->auto_global_callback(auto_global->name);
}
return 1;
}
return 0;
}

它去CG(auto_globals)找到这个超级全局变量,如果armed为1表示尚未初始化,所以就调用它的初始化回调auto_global_callback初始化超级全局变量的值,初始化成功与否赋值给armed,下次就不会重复的初始化了。

那么auto_global_callback一般做了什么呢?以_GET的初始化回调函数为例:

static zend_bool php_auto_globals_create_get(zend_string *name)
{
if (PG(variables_order) && (strchr(PG(variables_order),'G') || strchr(PG(variables_order),'g'))) {
sapi_module.treat_data(PARSE_GET, NULL, NULL);
} else {
zval_ptr_dtor(&PG(http_globals)[TRACK_VARS_GET]);
array_init(&PG(http_globals)[TRACK_VARS_GET]);
}

zend_hash_update(&EG(symbol_table), name, &PG(http_globals)[TRACK_VARS_GET]);
Z_ADDREF(PG(http_globals)[TRACK_VARS_GET]);

return 0; /* don't rearm */
}

从代码角度来看,_GET本身保存在另外一个类似CG、EG的全局变量PG里,http_globals是一个数组,其下标TRACK_VARS_GET下保存了_GET变量,它是一个哈希表。

无论如何,该函数最终将PG(http_globals)[TRACK_VARS_GET]这个_GET哈希表更新到了EG(symbol_table)执行期全局符号表中,也就是所谓的全局作用域中。

了解了这些之后,我们知道在zend_is_auto_global调用完成后,可以直接去EG(symbol_table)里获取_GET变量了:

// find it in global symbol table
zval *server = zend_hash_str_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER") - 1);
assert(server != NULL);
assert(Z_TYPE_P(server) == IS_ARRAY);

根据我们的PHP开发经验,$_GET是一个数组类型。

接着,我利用PHP函数var_dump,将这个数组打印出来:

// var_dump($_SERVER)
zval func_name;
ZVAL_STR(&func_name, zend_string_init("var_dump", sizeof("var_dump") - 1, 0));
zval retval;
assert(call_user_function(&EG(function_table), NULL, &func_name, &retval, 1, server) == SUCCESS);
zval_ptr_dtor(&func_name);
zval_ptr_dtor(&retval);
return SUCCESS;

调用PHP函数的方式我们再熟悉不过,所以就不再详细说明了。

结语

本章你应该掌握:

  • 全局常量的注册和访问。
  • 超级全局变量的访问。
  • CG,EG,PG大概的样子和关系。

在下一章中,我将特别的分析zend_array数组类型的API用法,以及简单介绍其数据结构实现原理。


分享给小伙伴们:
本文标签: PHP扩展开发PHP扩展PHP全局变量

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号