[ PHP 内核与扩展开发系列] PHP 启动与终止那点事:全局变量的定义和使用

这一章节,我们将学习如何在 PHP 扩展中使用全局变量

在扩展中定义全局变量

首先,我们需要在扩展的头文件中(默认是 php_*.h)中定义所有的全局变量。举个例子,比如我们要定义一个 unsigned long 类型的全局变量,可以这么做:

ZEND_BEGIN_MODULE_GLOBALS(sample4)
    unsigned long counter;
ZEND_END_MODULE_GLOBALS(sample4)

ZEND_BEGIN_MODULE_GLOBALSZEND_END_MODULE_GLOBALS 宏将定义的全局变量包起来。将上例中的宏展开后,是下面这个样子:

typedef struct _zend_sample4_globals {
    unsigned long counter;
} zend_sample4_globals;

如果你还有其他的全局变量需要定义,只需加在两个宏之间就可以了。接下来在 simple4.c 中声明我们在头文件中定义的这些全局变量:

ZEND_DECLARE_MODULE_GLOBALS(sample4);

这个宏的内部实现取决于是否启用了线程安全,在非线程安全的环境下,如:Apache1、Apache2-prefork、CGI、CLI 等会使用 zend_sample4_globals 结构来定义全局变量:

zend_sample4_globals sample4_globals;

我们可以直接通过 sample4_globals.counter 来获取计数器的值。

在线程安全的版本中,另一种方法是声明一个整数:

int sample4_globals_id;

填充这个 id 就等于定义了扩展中的全局变量。根据其定义的信息,将为每个新线程的独立存储空间分配内存块。我们可以在 MINIT 中这样定义:

#ifdef ZTS
    ts_allocate_id(
            &sample4_globals_id,
            sizeof(zend_sample4_globals),
            NULL, NULL);
#endif

有一点需要注意这种方法需要包裹在 #ifdef 中,以防止它在没有启动 ZTS 时执行,因为sample4_globals_id 只能在多线程环境中使用。

非线程安全的版本用我们在前面提到的 sample4_globals 来声明全局变量。

初始化和关闭全局变量

在非线程安全的环境中,会将一个 zend_sample4_globals 结构的副本保存在指定进程中。你可以指定他的默认值,或者在 MINIT 或者 RINIT 中分配资源来初始化它。最后要记得在对应的 MSHUTDOWN 或者 RSHUTDOWN 中及时释放这些资源。

然而在线程安全版本中,一个新的结构会在一个新线程 spun 的时候被分配。为了知道怎样初始化和关闭扩展中的全局变量,需要向 Zend 引擎提供回调函数。前面我们在调用 ts_allocate_id() 的时候是是以 NULL 来填充这个值的,接下来我们添加两个一会需要在 MINIT 调用的方法:

static void php_sample4_globals_ctor(zend_sample4_globals *sample4_globals TSRMLS_DC)
{
    // 在线程运行起来后始化一个新的 zend_sample4_globals 结构体
    sample4_globals->counter = 0;
}

static void php_sample4_globals_dtor(zend_sample4_globals *sample4_globals TSRMLS_DC)
{
    // 任何初始化期间分配的资源将会在这里释放
}

我们在启用和关闭扩展的时候调用它们:

PHP_MINIT_FUNCTION(sample4) {
    REGISTER_STRING_CONSTANT("SAMPLE4_VERSION", PHP_SAMPLE4_EXTVER, CONST_CS | CONST_PERSISTENT);
#ifdef ZTS
    ts_allocate_id(&sample4_globals_id,
                        sizeof(zend_sample4_globals),
                        (ts_allocate_ctor)php_sample4_globals_ctor,
                        (ts_allocate_dtor)php_sample4_globals_dtor);
#else
    php_sample4_globals_ctor(&sample4_globals TSRMLS_CC);
#endif
    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(sample4) {
#ifndef ZTS
    php_sample4_globals_dtor(&sample4_globals TSRMLS_CC);
#endif
    return SUCCESS;
}

使用全局变量

现在我们已经知道如何在扩展中创建全局变量了,使用它们很简单,我们还是来看前面定义的那个计数器的递增功能如何实现:

PHP_FUNCTION(sample4_counter) {
    RETURN_LONG(++sample4_globals.counter);
}

是不是看起来很简单,但是,在线程安全版本中将无法正常工作。那么我们来看看怎么在线程安全环境中完成这个功能吧:

PHP_FUNCTION(sample4_counter)
{
#ifdef ZTS
        RETURN_LONG(++TSRMG(sample4_globals_id, \
                zend_sample4_globals*, counter));
#else
        /* 非ZTS */
        RETURN_LONG(++sample4_globals.counter);
#endif
}

看起来很丑对吗?想象一下,在你的整个代码库中,这些 IFDEF 指令在每一个线程安全的全局访问时穿插。它会看起来比 Perl 更糟糕!这就是为什么所有的核心扩展,都使用一个额外的宏观层抽象这种情况。我们可以在 php_sample4.h 中找到下面这段代码:

#ifdef ZTS
#include "TSRM.h"
#define SAMPLE4_G(v) TSRMG(sample4_globals_id, zend_sample4_globals*, v)
#else
#define SAMPLE4_G(v) (sample4_globals.v)
#endif

使用它们会让你的方法看起来更简洁:

PHP_FUNCTION(sample4_counter) {
    RETURN_LONG(++SAMPLE4_G(counter));
}

看到 *G() 这样的宏是不是有种似曾相识的感觉?也许以前你看到过 EG()、CG() 等宏,了解他们会让你对 PHP 的了解更深一步:

访问器宏 关联数据
EG() 这个宏可以用来访问符号表/函数/资源信息和常量
CG() 用来访问核心全局变量
PG() PHP全局变量。我们知道 php.ini 会映射一个或者多个 PHP 全局结构。举几个使用这个宏的例子:PG(register_globals)PG(safe_mode)PG(memory_limit)
FG() 文件全局变量。大多数文件 I/O 或相关的全局变量的数据流都塞进标准扩展出口结构

学院君 has written 716 articles

资深PHP工程师,Laravel学院院长

发表评论

标记为*的字段是必填项(邮箱地址不会被公开)

你可以使用这些HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>