搜尋
首頁後端開發php教程[翻譯][php擴展開發與嵌入式]第12章-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\" / alt="[翻譯][php擴展開發與嵌入式]第12章-php的啟動過程" >"

            "</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
您如何防止與會議有關的跨站點腳本(XSS)攻擊?您如何防止與會議有關的跨站點腳本(XSS)攻擊?Apr 23, 2025 am 12:16 AM

要保護應用免受與會話相關的XSS攻擊,需採取以下措施:1.設置HttpOnly和Secure標誌保護會話cookie。 2.對所有用戶輸入進行輸出編碼。 3.實施內容安全策略(CSP)限制腳本來源。通過這些策略,可以有效防護會話相關的XSS攻擊,確保用戶數據安全。

您如何優化PHP會話性能?您如何優化PHP會話性能?Apr 23, 2025 am 12:13 AM

优化PHP会话性能的方法包括:1.延迟会话启动,2.使用数据库存储会话,3.压缩会话数据,4.管理会话生命周期,5.实现会话共享。这些策略能显著提升应用在高并发环境下的效率。

什麼是session.gc_maxlifetime配置設置?什麼是session.gc_maxlifetime配置設置?Apr 23, 2025 am 12:10 AM

theSession.gc_maxlifetimesettinginphpdeterminesthelifespanofsessiondata,setInSeconds.1)它'sconfiguredinphp.iniorviaini_set().2)abalanceisesneededeededeedeedeededto toavoidperformance andunununununexpectedLogOgouts.3)

您如何在PHP中配置會話名?您如何在PHP中配置會話名?Apr 23, 2025 am 12:08 AM

在PHP中,可以使用session_name()函數配置會話名稱。具體步驟如下:1.使用session_name()函數設置會話名稱,例如session_name("my_session")。 2.在設置會話名稱後,調用session_start()啟動會話。配置會話名稱可以避免多應用間的會話數據衝突,並增強安全性,但需注意會話名稱的唯一性、安全性、長度和設置時機。

您應該多久再生一次會話ID?您應該多久再生一次會話ID?Apr 23, 2025 am 12:03 AM

會話ID應在登錄時、敏感操作前和每30分鐘定期重新生成。 1.登錄時重新生成會話ID可防會話固定攻擊。 2.敏感操作前重新生成提高安全性。 3.定期重新生成降低長期利用風險,但需權衡用戶體驗。

如何在PHP中設置會話cookie參數?如何在PHP中設置會話cookie參數?Apr 22, 2025 pm 05:33 PM

在PHP中設置會話cookie參數可以通過session_set_cookie_params()函數實現。 1)使用該函數設置參數,如過期時間、路徑、域名、安全標誌等;2)調用session_start()使參數生效;3)根據需求動態調整參數,如用戶登錄狀態;4)注意設置secure和httponly標誌以提升安全性。

在PHP中使用會議的主要目的是什麼?在PHP中使用會議的主要目的是什麼?Apr 22, 2025 pm 05:25 PM

在PHP中使用會話的主要目的是維護用戶在不同頁面之間的狀態。 1)會話通過session_start()函數啟動,創建唯一會話ID並存儲在用戶cookie中。 2)會話數據保存在服務器上,允許在不同請求間傳遞數據,如登錄狀態和購物車內容。

您如何在子域中分享會議?您如何在子域中分享會議?Apr 22, 2025 pm 05:21 PM

如何在子域名間共享會話?通過設置通用域名的會話cookie實現。 1.在服務器端設置會話cookie的域為.example.com。 2.選擇合適的會話存儲方式,如內存、數據庫或分佈式緩存。 3.通過cookie傳遞會話ID,服務器根據ID檢索和更新會話數據。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中