首頁 後端開發 php教程 [翻譯][php擴展開發與嵌入式]第12章-php的啟動過程

[翻譯][php擴展開發與嵌入式]第12章-php的啟動過程

Feb 10, 2017 am 10:14 AM
php


啟動, 終止, 以及其中的一些點

在本書中, 你已經多次使用MINIT函數在php加載你擴展的共享庫時執行初始化任務. 在第1章」 php的生命週期"中, 你也學習了其他三個啟動/終止函數, 與MINIT對應的是MSHUTDOWN, 另外還有一對RINIT/RSHUTDOWN方法在每個頁面請求啟動和終止時被調用.

生命週期

除了這四個直接鏈接到模組結構的函數外, 還有兩個函數僅用於線程環境, 用來處理每個線程的啟動和終止, 以及它們使用的似有存儲空間.開始之前, 先將你的php擴充骨架程式拷貝到php源碼樹的ext/sample4下. 程式碼如下

config.m4

PHP_ARG_ENABLE(sample4,
  [Whether to enable the "sample4" extension],
  [  enable-sample4       Enable "sample4" extension support])

if test $PHP_SAMPLE4 != "no"; then
  PHP_SUBST(SAMPLE4_SHARED_LIBADD)
  PHP_NEW_EXTENSION(sample4, sample4.c, $ext_shared)
fi
登入後複製

r sample4.c

#ifndef PHP_SAMPLE4_H
/* Prevent double inclusion */
#define PHP_SAMPLE4_H

/* Define Extension Properties */
#define PHP_SAMPLE4_EXTNAME    "sample4"
#define PHP_SAMPLE4_EXTVER    "1.0"

/* Import configure options
   when building outside of
   the PHP source tree */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/* Include PHP Standard Header */
#include "php.h"

/* Define the entry point symbol
 * Zend will use when loading this module
 */
extern zend_module_entry sample4_module_entry;
#define phpext_sample4_ptr &sample4_module_entry

#endif /* PHP_SAMPLE4_H */
登入後複製
要注意, 每個啟動和終止函數在退出時都返回SUCCESS. 如果這些函數中某個返回FAILURE, 引擎就認為這個過程失敗併中斷php的執行.

模組生命週期

在前面章節已經多次使用, 因此MINIT對你來說應該已經很熟悉了. 它在模組第一次加載到進程空間時觸發, 對於單請求sapi比如CLI和CGI, 或多執行緒sapi例如apache2-worker, 它都只會執行一次, 因為不涉及到fork.

對於多進程sapi, apache1, apache2-prefork, 透過它們的mod_php實例for了多個webserver 例如進程. 每個mod_php實例都必須載入自己的擴充模組, 因此MINIT將被執行多次, 不過對於每個行程, 它仍然只執行一次.

當模組被卸載時, MSHUTDOWN方法被呼叫, 此時模組的所有資源(例如持久化記憶體區塊)都將被釋放, 回傳給作業系統.

引擎端的特性, 例如類別, 資源ID, 流包裝和過濾器, 使用者空間全域變數, php.ini中的指令這些公共的資源都是在模組的INIT和SHUTDOWN階段被分配和釋放的.

理論上, 你可以不用在MSHUTDOWN階段做資源釋放的工作, 把它留給OS去做隱式的記憶體和檔案釋放. 不過在apache 1.3中使用你的擴展時, 你會發現一個有趣的現象, apache將加載mod_php, 在進程中運行MINIT, 接著立即卸載mod_php, 觸發MSHUTDOWN方法, 接著再次加載它. 如果沒有正確的MSHUTDOWN階段, 在MINIT階段初始分配的資源就將洩露.

線程生命週期

在多線程中分配它,需要為每個線程分配它自己獨立的線程資源, 或追蹤它自己的單請求計數器.對於這些特殊情況, 存在一組每個線程的鉤子, 允許在線程啟動和終止時執行它們. 典型的情況是當apache2-worker這樣的sapi啟動時, 它將會產生一打或更多的線程去處理並發請求.

任何在多请求间共享, 在同一进程中不同线程有不能访问的资源, 都是在线程的构造器和析构器中分配和释放的. 比如这可能包括EG(persistent_list)HashTable中的持久化资源, 因为它们通常包括网络或文件资源, 需要考虑指令间它们的状态一致性.

请求生命周期

最后一个也是最短的生命周期是请求生命周期, 在这个周期内, 你的扩展可能会去初始化默认的用户空间变量, 或初始化内部状态跟踪信息. 因为这些方法在每个页面请求都被调用, 因此要尽可能的保证这些处理和内存分配可以执行的足够快.

通过MINFO对外暴露模块信息

除非你计划只有很少人使用你的扩展, 并且并没有计划修改API, 否则你就需要能够告诉用户空间一些关于扩展自身的信息. 比如, 是否所有的环境和版本特有特性都可用? 它编译的外部库的版本是什么? 是否有网站或邮件地址可以让你扩展的用户在需要时寻求帮助?

如果你曾经看过phpinfo()或php -i的输出, 你就会注意到, 所有这些信息都被组织到一种良好格式, 易于解析的输出中. 你的扩展可以很简单的在这些内容中增加自己的信息, 只需要在你的模块中增加一个MINFO()函数即可:

PHP_MINFO_FUNCTION(sample4)
{
    php_info_print_table_start();
    php_info_print_table_row(2, "Sample4 Module", "enabled");
    php_info_print_table_row(2, "version", PHP_SAMPLE4_EXTVER);
    php_info_print_table_end();
}
登入後複製

通过使用这些包装函数, 你的模块信息将在从webserver sapi(比如cgi, iis, apache等)输出时自动的包装为HTML标签, 而在使用cli时输出为普通文本. 为了使得构建时你的扩展中可以使用这些函数原型, 你需要#include "ext/standard/info.h".

下面是这个头文件中可用的php_info_*()族函数.

  • char *php_info_html_esc(char *str TSRMLS_DC)

用户空间htmlentites()函数的底层实现php_escape_html_entities()的一个包装. 返回的字符串是用emalloc()分配的, 使用后必须显式的使用efree()释放.

  • void php_info_print_table_start(void)

  • void php_info_print_table_end(void)

输出html表格的开始/结束标签. html输出禁用时, 比如在cli中, 它将在start中输出换行符, end中不输出任何内容.

  • void php_info_print_table_header(int cols, ...)

  • void php_info_print_table_colspan_header(int cols, char *header)

输出一行表头. 第一个版本为每个可变参输出一个, 内容是cols后面的字符串参数. clospan版的则只输出一个, 并给它指定colspan属性.

  • void php_info_print_table_row(int cols, ...)

  • void php_info_print_table_row_ex(int cols, char *class, ...)

这两个版本都为每个可变参输出一个. 两者的不同在于前者将为其设置class="v"属性, 而后者则允许调用者指定自己的类名用于自定义格式. 没有打开HTML格式输出时, 由于只是文本输出, 两者的差异就不复存在了.

  • void php_info_print_box_start(int flag)

  • void php_info_print_box_end()

这两个函数只是简单的输出一个表格(, )的开始和结束. 如果给定的flag值非0, 则使用class="h", 否则使用class="v". 使用非html输出时, 标记为0将导致在star中输出一个换行符, 此时这两个函数不会在产生其他任何输出.

  • void php_info_print_hr(void)

这个函数在html启用时输出


标签, 或者, 当没有启用html输出时, 输出31个下划线, 并在前后各输出两个换行符.

在MINFO中通常可以使用PHPWRITE()和php_printf(), 但手动输出内容时应该注意它需要依赖于当前的SAPI期望输出文本还是html. 可以通过测试全局的sapi_module结构体的phpinfo_as_text属性来确认这一点:

PHP_MINFO_FUNCTION(sample4)
{
    php_info_print_table_start();
    php_info_print_table_row(2, "Sample4 Module", "enabled");
    php_info_print_table_row(2, "version", PHP_SAMPLE4_EXTVER);
    if (sapi_module.phpinfo_as_text) {
        /* No HTML for you */
        php_info_print_table_row(2, "By",
            "Example Technologies\nhttp://www.php.cn/");
    } else {
        /* HTMLified version */
        php_printf("<tr>"
            "<td class=\"v\">By</td>"
            "<td class=\"v\">"
            "<a href=\"http://www.example.com\""
            " alt=\"Example Technologies\">"
            "<img src=\"http://www.example.com/logo.png\" />"

            "</a></td></tr>");
    }
    php_info_print_table_end();
}
登入後複製

常量

向用户空间脚本暴露信息更好的方法是使用扩展定义脚本可以在运行时访问的常量, 并可以通过这些常量改变扩展的某些行为. 在用户空间中, 我们使用define()函数定义常量; 内部, 则是和它非常相似的REGISTER_*_CONSTANT()一族的宏.

多数常量是你想要它们在所有脚本中初始化为相同值的数据. 它们是在MINIT函数中定义的.

PHP_MINIT_FUNCTION(sample4)
{
    REGISTER_STRING_CONSTANT("SAMPLE4_VERSION",
            PHP_SAMPLE4_EXTVER, CONST_CS | CONST_PERSISTENT);

    return SUCCESS;
}
登入後複製


这个宏的第一个参数是要暴露给用户空间的常量名. 在这个例子中, 用户空间就可以执行echo SAMPLE4_VERSION; 得到输出1.0. 这里有一点要特别注意, REGISTER_*_CONSTANT()一族的宏使用了sizeof()调用去确定常量名的长度. 也就是说只能使用字面量值. 如果使用char *变量则会导致不正确的结果(sizeof(char *)在32位平台上通常是4, 而不是真正字符串的长度).

下一个参数是常量的值. 多数情况下, 它只需要一个参数, 不过, 对于STRINGL版本, 你还需要一个参数去指定长度. 在注册字符串常量时, 字符串的值并不会拷贝到常量中, 只是引用它. 也就是说需要在持久化内存中为其分配空间, 并在对应的SHUTDOWN阶段释放它们.

最后一个参数是一个有两个可选值的位域操作结果. CONST_CS标记说明该常量大小写敏感. 对于用户空间定义的常量以及几乎所有的内部常量来说, 这都是默认行为. 只有极少数的情况, 比如trUE, FALSE, NULL, 在注册时省略了这个标记用以说明它们是不区分大小写的.

注册常量时的第二个标记是持久化标记. 当在MINIT中定义常量时, 它们必须被构建为跨请求的持久化常量. 但是, 如果在请求中定义常量, 比如在RINIT中, 你可能就需要省略这个标记以允许引擎在请求结束时销毁该常量了.

下面是4个可用的常量注册宏的原型. 一定要记住, 名字参数必须是字符串字面量而不能是char *变量:

REGISTER_LONG_CONSTANT(char *name, long lval, int flags)
REGISTER_DOUBLE_CONSTANT(char *name, double dval, int flags)
REGISTER_STRING_CONSTANT(char *name, char *value, int flags)
REGISTER_STRINGL_CONSTANT(char *name,
                     char *value, int value_len, int flags)
登入後複製

如果字符串必须从变量名初始化, 比如在循环中, 你可以使用如下的函数调用(上面的宏就是映射到这些函数中的):

void zend_register_long_constant(char *name, uint name_len,
        long lval, int flags, int module_number TSRMLS_DC)
void zend_register_double_constant(char *name, uint name_len,
        double dval, int flags, int module_number TSRMLS_DC)
void zend_register_string_constant(char *name, uint name_len,
        char *strval, int flags, int module_number TSRMLS_DC)
void zend_register_stringl_constant(char *name, uint name_len,
        char *strval, uint strlen, int flags,
        int module_number TSRMLS_DC)
登入後複製


此时, 名字参数的长度可以直接由调用作用域提供. 你应该注意到, 这次就必须显式的传递TSRMLS_CC参数了, 并且, 这里还引入了另外一个参数.

module_number是在你的扩展被加载或被卸载时传递给你的信息. 你不用关心它的值, 只需要传递它就可以了. 在MINIT和RINIT函数原型中都提供了它, 因此, 在你定义常量的时候, 它就是可用的. 下面是函数版的常量注册例子:

PHP_MINIT_FUNCTION(sample4)
{
    register_string_constant("SAMPLE4_VERSION",
        sizeof("SAMPLE4_VERSION"),
        PHP_SAMPLE4_EXTVER,
        CONST_CS | CONST_PERSISTENT,
        module_number TSRMLS_CC);

    return SUCCESS;
}
登入後複製


要注意当sizeof()用于确定SAMPLE4_VERSION的长度时, 这里并没有减1. 常量的名字是包含它的终止NULL的. 如果使用strlen()确定长度, 要记得给结果加1以使其包含终止的NULL.

除了数组和对象, 其他的类型都可以被注册, 但是因为在ZEND API中不存在这些类型的宏或函数, 你就需要手动的定义常量. 按照下面的范本, 仅需要在使用时修改去创建恰当类型的zval *即可:

void php_sample4_register_boolean_constant(char *name, uint len,
    zend_bool bval, int flags, int module_number TSRMLS_DC)
{
    zend_constant c;

    ZVAL_BOOL(&c.value, bval);
    c.flags = CONST_CS | CONST_PERSISTENT;
    c.name = zend_strndup(name, len - 1);
    c.name_len = len;
    c.module_number = module_number;
    zend_register_constant(&c TSRMLS_CC);
}
登入後複製


扩展的全局空间

如果可以保证任何时刻一个进程中只有一个php脚本在执行, 你的扩展就可以随意的定义全局变量并去访问它们, 因为已知在opcode执行过程中不会有其他脚本被执行. 对于非线程sapi, 这是可行的, 因为所有的进程空间中都只能同时执行一个代码路径.

然而在线程sapi中, 可能会有两个或更多的线程同时读或更糟糕的情况是同时写相同的值. 为了解决这个问题, 就引入了一个扩展的全局空间概念, 它为每个扩展的数据提供一个唯一的数据存储桶.

定义扩展的全局空间

要给你的扩展申请一块存储的桶, 首先就需要在php_sample4.h上的一个标准结构体中定义所有你的全局变量. 比如, 假设你的扩展要保存一个计数器, 保持对某个方法在请求内被调用次数的跟踪, 你就需要定义一个结构体包含一个unsigned long:

ZEND_BEGIN_MODULE_GLOBALS(sample4)
    unsigned long counter;
ZEND_END_MODULE_GLOBALS(sample4)
登入後複製

ZEND_BEGIN_MODULE_GLOBALS和ZEND_END_MODULE_GLOBALS宏为扩展全局变量结构的定义提供了统一的框架. 如果你看过这个块的展开形式, 就可以很容易的理解它了:

typedef struct _zend_sample4_globals {
    unsigned long counter;
} zend_sample4_globals;
登入後複製


你可以像在其他的C语言结构体中增加成员一样, 为它增加其他成员. 现在, 你有了存储桶的(数据结构)定义, 接下来要做的就是声明一个这个类型的变量, 你需要在扩展的sample4.c文件中, #include "php_sample4.h"语句下一行声明它:

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中, 以放置在没有启用Zend线程安全(ZTS)时执行它. 这是因为sample4_globals_id只在线程环境下才会被声明, 非线程环境的构建则使用的是sample4_globals变量的直接值.

每个线程的初始化和终止

在非线程构建中, 你的zend_sample4_globals结构体在一个进程中只有一份拷贝. 你可以给它设置初始值或在MINIT或RINIT中为其分配资源, 进行初始化, 在MSHUTDOWN和RSHUTDOWN阶段如果需要, 则进行相应的释放.

然而, 对于线程构建, 每次一个新的线程产生时, 都会分配一个新的结构体. 实际上, 这在webserver启动时可能会发生很多次, 而在webserver进程的整个生命周期中, 这可能会发生成百上千次. 为了知道怎样初始化和终止你的扩展全局空间, 引擎需要执行一些回调函数. 这就是上面的例子中你传递给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;
}
登入後複製


要注意, 在没有开启ZTS时, ctor和dtor函数是手动调用的. 不要忘记: 非线程环境也需要初始化和终止.

你可能奇怪为什么在php_sample4_globals_ctor()和php_sample4_globals_dtor()中直接使用了TSRMLS_CC. 如果你认为"这完全不需要, 它在ZTS禁用时解析出来是空的内容, 并且由#ifndef指令, 我们知道ZTS是被禁用的, 你的观点绝对正确. 声明中的相关的TSRMLS_DC指令仅用于保证代码的一致性. 从积极的一面考虑, 如果ZEND API修改这些值使得在非ZTS构建中也有有效内容时, 你的代码就不需要修改就做好了相应的调整.

访问扩展的全局空间

现在你的扩展有了一个全局变量集合, 你可以开始在你的代码中访问它们了. 在非ZTS模式中这很简单, 只需要访问进程全局作用域的sample4_globals变量的相关成员即可, 比如, 下面的用户空间函数增加了你前面定义的计数器并返回它的当前值:

PHP_FUNCTION(sample4_counter)
{
    RETURN_LONG(++sample4_globals.counter);
}
登入後複製
很简单很容易. 不幸的是, 这种方式在线程环境的PHP构建中不能工作. 
这种情况下你就需要做更多的工作. 下面是使用ZTS语义的该函数返回语句:
登入後複製
RETURN_LONG(++TSRMG(sample4_globals_id,
                zend_sample4_globals*, counter));
登入後複製


TSRMG()宏需要你已经传递的TSRMLS_CC参数, 它会从当前线程池的资源结构中查找需要的数据. 这里, 它使用sample4_globals_id索引映射到内存池中你扩展的全局结构体的位置, 最终, 使用数据类型映射的元素名得到结构体中的偏移量. 因为你并不知道运行时你的扩展是否使用ZTS模式, 因此, 你需要让你的代码适应两种情况. 要做到这一点, 就需要按照下面方式重写该函数:

PHP_FUNCTION(sample4_counter)
{
#ifdef ZTS
    RETURN_LONG(++TSRMG(sample4_globals_id, \
                    zend_sample4_globals*, counter));
#else /* non-ZTS */
    RETURN_LONG(++sample4_globals.counter);
#endif
}
登入後複製


看起来不舒服? 是的, 如果你所有的代码都基于这样的ifdef指令去处理线程安全的全局访问, 它看起来可能比Perl还糟糕! 这就是为什么在所有的PECL扩展中都使用了一个抽象的宏来封装全局访问的原因. 在你的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));
}
登入後複製


这个宏给你一种似曾相识的感觉吗? 应该是这样的. 它和你已经使用过的EG(symbol_table)以及EG(active_symbol_table)是仙童的概念和实践. 在阅读php源码树中其他部分以及其他扩展时, 你会经常碰到这种宏. 下表列出了常用的全局访问宏:


PG()

访问宏

关联数据

EG()

执行全局空间.这个结构体主要用于引擎内部对当前请求的状态跟踪.这个全局空间中可以找到符号表,函数表,类表,常量表,资源表等.

CG()

核心全域空間.主要被Zend引擎在腳本編譯和內核底層執行過程中使用.

php

.php 指令映射到php 全域變數結構體中的一個或多個元素.例如: PG(register_globals), PG(safe_mode)以及PG(memory_limit)FG()

FG()

多數文件

I/O 或流相關的全局變數被裝入到這個結構通過標準擴展暴露.


用户空间超级全局变量

用户空间有它自己的完全无关的全局概念. 在用户空间, 有一种特殊的全局变量被称为超级全局变量. 这种特殊的用户空间变量包括$_GET, $_POST, $_FILE等等, 在全局作用域, 函数或方法内部都可以等同本地作用域进行访问.

这是由于超级全局变量的解析方式造成的, 它们必须在脚本编译之前定义. 这就意味着在普通的脚本中不能定义其他超级全局变量. 不过, 在扩展中, 可以在请求接收到之前去将变量名定义为超级全局变量.

扩展定义超级全局变量的一个基本示例是ext/session, 它在session_start()和session_write_close()或脚本结束之间, 使用$_SESSION超级全局变量存储会话信息. 为了将$_SESSION定义为超级全局变量, session扩展在MINIT函数中执行了一次下面的语句:

PHP_MINIT_FUNCTION(session)
{
    zend_register_auto_global("_SESSION",
                         sizeof("_SESSION") - 1,
                         NULL TSRMLS_CC);
    return SUCCESS;
}
登入後複製

注意, 第二个参数, 变量名的长度, 使用了sizeof() - 1, 因此不包含终止NULL. 这和之前你看到的多数内部调用不同, 因此, 在定义自己的超级全局变量时要格外小心这一点.

zend_register_auto_global()函数在Zend引擎2中的原型如下:

int zend_register_auto_global(char *name, uint name_len,
    zend_auto_global_callback auto_global_callback TSRMLS_DC)
登入後複製


在Zend引擎1中, auto_global_callback参数并不存在. 为了让你的扩展兼容php4, 就需要在MINIT函数中通过#ifdef块去选择性的执行不同的调用, 定义$_SAMPLE4超级全局变量.

PHP_MINIT_FUNCTION(sample4)
{
    zend_register_auto_global("_SAMPLE4", sizeof("_SAMPLE4") - 1
#ifdef ZEND_ENGINE_2
                        , NULL
#endif
                        TSRMLS_CC);
    return SUCCESS;
}
登入後複製


自动全局回调

ZE2中zend_register_auto_global()的auto_global_callback参数是一个指向自定义函数的指针, 该函数在编译阶段用户空间脚本碰到你的超级全局变量时将被触发. 实际上, 它可以用于在当前脚本没有访问超级全局变量时避免繁杂的初始化处理. 考虑下面的代码:

zend_bool php_sample4_autoglobal_callback(char *name,
                            uint name_len TSRMLS_DC)
{
    zval *sample4_val;
    int i;

    MAKE_STD_ZVAL(sample4_val);
    array_init(sample4_val);
    for(i = 0; i < 10000; i++) {
        add_next_index_long(sample4_val, i);
    }
    ZEND_SET_SYMBOL(&EG(symbol_table), "_SAMPLE4",
                                    sample4_val);
    return 0;
}
PHP_MINIT_FUNCTION(sample4)
{
    zend_register_auto_global("_SAMPLE4", sizeof("_SAMPLE4") - 1
#ifdef ZEND_ENGINE_2
                        , php_sample4_autoglobal_callback
#endif
                        TSRMLS_CC);
    return SUCCESS;
}
登入後複製


php_sample4_autoglobal_callback()所做的工作代表的是对内存和CPU时间的耗费, 如果$_SAMPLE4没有被访问, 则这些资源都将被浪费. 在Zend引擎2中, 只有当脚本被编译时发现某个地方访问了$_SAMPLE4才会调用php_sample4_autoglobal_callback()函数. 注意, 一旦数组初始化完成并增加到请求的符号表后, 函数就返回0值. 这样就解除了请求中后续对该超级全局变量访问时的回调, 以确保对$_SAMPLE4的多次访问不会导致对该回调函数的多次调用. 如果你的扩展需要在每次碰到该超级全局变量时都执行回调函数, 只需要让回调函数返回真值(非0)使得超级全局变量回调函数不被解除即可.

不幸的是, 现在的设计和php4/zend引擎1冲突, 因为旧的引擎并不支持自动全局回调. 这种情况下, 你就需要在每次脚本启动时, 无论是否使用了变量都去初始化. 要这样做, 直接在RINIT函数中调用你上面编写的回调函数即可:

PHP_RINIT_FUNCTION(sample4)
{
#ifndef ZEND_ENGINE_2
    php_sample4_autoglobal_callback("_SAMPLE4",
                              sizeof("_SAMPLE4") - 1,
                              TSRMLS_CC);
#endif
    return SUCCESS;
}
登入後複製


小结

透過本章的學習, 你認識了一些新的但是已經熟悉的概念, 包括內部的線程安全全局變量, 怎樣向用戶空間暴露諸如常量, 預初始化變量, 超級全局變量等信息. 下一章, 你將學會怎麼定義和解析php.ini中的指令, 並將它們和你已經設定的內部線程安全的全局結構關聯起來.


以上就是[翻譯][php擴展開發和嵌入式]第12章-php的啟動過程的內容,更多相關內容請關注PHP中文網(www.php.cn)!


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
威爾R.E.P.O.有交叉遊戲嗎?
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

適用於 Ubuntu 和 Debian 的 PHP 8.4 安裝和升級指南 適用於 Ubuntu 和 Debian 的 PHP 8.4 安裝和升級指南 Dec 24, 2024 pm 04:42 PM

PHP 8.4 帶來了多項新功能、安全性改進和效能改進,同時棄用和刪除了大量功能。 本指南介紹如何在 Ubuntu、Debian 或其衍生版本上安裝 PHP 8.4 或升級到 PHP 8.4

如何設定 Visual Studio Code (VS Code) 進行 PHP 開發 如何設定 Visual Studio Code (VS Code) 進行 PHP 開發 Dec 20, 2024 am 11:31 AM

Visual Studio Code,也稱為 VS Code,是一個免費的原始碼編輯器 - 或整合開發環境 (IDE) - 可用於所有主要作業系統。 VS Code 擁有大量針對多種程式語言的擴展,可以輕鬆編寫

我後悔之前不知道的 7 個 PHP 函數 我後悔之前不知道的 7 個 PHP 函數 Nov 13, 2024 am 09:42 AM

如果您是經驗豐富的PHP 開發人員,您可能會感覺您已經在那裡並且已經完成了。操作

您如何在PHP中解析和處理HTML/XML? 您如何在PHP中解析和處理HTML/XML? Feb 07, 2025 am 11:57 AM

本教程演示瞭如何使用PHP有效地處理XML文檔。 XML(可擴展的標記語言)是一種用於人類可讀性和機器解析的多功能文本標記語言。它通常用於數據存儲

在PHP API中說明JSON Web令牌(JWT)及其用例。 在PHP API中說明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

php程序在字符串中計數元音 php程序在字符串中計數元音 Feb 07, 2025 pm 12:12 PM

字符串是由字符組成的序列,包括字母、數字和符號。本教程將學習如何使用不同的方法在PHP中計算給定字符串中元音的數量。英語中的元音是a、e、i、o、u,它們可以是大寫或小寫。 什麼是元音? 元音是代表特定語音的字母字符。英語中共有五個元音,包括大寫和小寫: a, e, i, o, u 示例 1 輸入:字符串 = "Tutorialspoint" 輸出:6 解釋 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。總共有 6 個元

解釋PHP中的晚期靜態綁定(靜態::)。 解釋PHP中的晚期靜態綁定(靜態::)。 Apr 03, 2025 am 12:04 AM

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? 什麼是PHP魔術方法(__ -construct,__destruct,__call,__get,__ set等)並提供用例? Apr 03, 2025 am 12:03 AM

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。

See all articles