Start, Beendigung und einige der Punkte dazwischen
In diesem Buch erfahren Sie Sie haben mehr gelernt: Verwenden Sie die MINIT-Funktion zum ersten Mal, um Initialisierungsaufgaben auszuführen, wenn PHP Ihre erweiterte gemeinsam genutzte Bibliothek lädt. In Kapitel 1 „Der Lebenszyklus von PHP“ haben Sie auch drei weitere Start-/Beendigungsfunktionen gelernt. Das Gegenstück zu MINIT ist MSHUTDOWN , und es gibt auch ein Paar RINIT/RSHUTDOWN-Methoden, die aufgerufen werden, wenn jede Seitenanforderung gestartet und beendet wird
Lebenszyklus
Zusätzlich zu diesen vier Funktionen, die direkt mit der Modulstruktur verknüpft sind, gibt es zwei Funktionen, die nur in der Thread-Umgebung verwendet werden und das Starten und Beenden jedes Threads sowie den Speicherplatz verwalten Sie verwenden. Bevor Sie beginnen, kopieren Sie zunächst Ihr PHP-Erweiterungsgerüstprogramm auf ext/sample4 im PHP-Quellbaum. Der Code lautet wie folgt:
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
php_sample4.h
#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 */
sample4.c
#include "php_sample4.h" #include "ext/standard/info.h" static function_entry php_sample4_functions[] = { { NULL, NULL, NULL } }; PHP_MINIT_FUNCTION(sample4) { return SUCCESS; } PHP_MSHUTDOWN_FUNCTION(sample4) { return SUCCESS; } PHP_RINIT_FUNCTION(sample4) { return SUCCESS; } PHP_RSHUTDOWN_FUNCTION(sample4) { return SUCCESS; } PHP_MINFO_FUNCTION(sample4) { } zend_module_entry sample4_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_SAMPLE4_EXTNAME, php_sample4_functions, PHP_MINIT(sample4), PHP_MSHUTDOWN(sample4), PHP_RINIT(sample4), PHP_RSHUTDOWN(sample4), PHP_MINFO(sample4), #if ZEND_MODULE_API_NO >= 20010901 PHP_SAMPLE4_EXTVER, #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_SAMPLE4 ZEND_GET_MODULE(sample4) #endif
Beachten Sie, dass jede Start- und Beendigungsfunktion beim Beenden ERFOLGREICH zurückgibt. Wenn eine dieser Funktionen FAILURE zurückgibt, betrachtet die Engine den Prozess als fehlgeschlagen und unterbricht die Ausführung von PHP.
Modullebenszyklus
wurde in den vorherigen Kapiteln schon oft verwendet, daher sollte Ihnen MINIT bekannt sein. Es wird verwendet, wenn das Modul zum ersten Mal im Prozessraum in Triggered geladen wird. Bei Single-Request-Sapi wie CLI und CGI oder Multi-Thread-Sapi wie Apache2-Worker wird es nur einmal ausgeführt, da Fork nicht beteiligt ist 🎜>
Für Multiprozess-Sapi, wie Apache1, Apache2-Prefork, für mehrere Webserver-Prozesse über ihre mod_php-Instanzen muss jede mod_php-Instanz ihr eigenes Erweiterungsmodul laden, damit MINIT mehrfach ausgeführt wird Mal, aber für jeden Prozess wird er immer noch nur einmal ausgeführt.
Wenn das Modul entladen wird, wird die MSHUTDOWN-Methode aufgerufen. Zu diesem Zeitpunkt werden alle Ressourcen des Moduls (z. B. persistent) aufgerufen Speicherblöcke) werden freigegeben und an das Betriebssystem zurückgegeben.
Engine-seitige Funktionen, wie Klassen, Ressourcen-IDs, Stream-Wrapper und Filter, globale Variablen im Benutzerbereich, Anweisungen in PHP .ini, diese öffentlichen Ressourcen befinden sich alle in der INIT- und SHUTDOWN-Phase des Moduls. Die Phase wird zugewiesen und freigegeben.
Theoretisch müssen Sie die Ressourcenfreigabe nicht durchführen Arbeiten Sie in der MSHUTDOWN-Phase und überlassen Sie die implizite Speicher- und Dateifreigabe dem Betriebssystem. Wenn Sie jedoch Ihre Erweiterung in Apache 1.3 verwenden, werden Sie ein interessantes Phänomen feststellen, das dazu führt, dass Apache mod_php lädt und dann sofort MINIT ausführt entladen Sie mod_php, lösen Sie die MSHUTDOWN-Methode aus und laden Sie sie dann erneut. Wenn sie in der MSHUTDOWN-Phase nicht korrekt ist, gehen die ursprünglich in der MINIT-Phase zugewiesenen Ressourcen verloren >Thread-Lebenszyklus
In einem Multithread-Sapi ist es manchmal notwendig, jedem Thread eine eigene unabhängige Ressource zuzuweisen oder seinen eigenen Einzelanforderungszähler zu verfolgen Für diese Sonderfälle gibt es eine Reihe von Thread-Hooks, die es dem Thread ermöglichen, sie auszuführen. Wenn ein Sapi wie Apache2-Worker gestartet wird, erzeugt er normalerweise ein Dutzend oder mehr Threads, um gleichzeitige Anforderungen zu verarbeiten. 任何在多请求间共享, 在同一进程中不同线程有不能访问的资源, 都是在线程的构造器和析构器中分配和释放的. 比如这可能包括EG(persistent_list)HashTable中的持久化资源, 因为它们通常包括网络或文件资源, 需要考虑指令间它们的状态一致性. 请求生命周期 最后一个也是最短的生命周期是请求生命周期, 在这个周期内, 你的扩展可能会去初始化默认的用户空间变量, 或初始化内部状态跟踪信息. 因为这些方法在每个页面请求都被调用, 因此要尽可能的保证这些处理和内存分配可以执行的足够快. 通过MINFO对外暴露模块信息 除非你计划只有很少人使用你的扩展, 并且并没有计划修改API, 否则你就需要能够告诉用户空间一些关于扩展自身的信息. 比如, 是否所有的环境和版本特有特性都可用? 它编译的外部库的版本是什么? 是否有网站或邮件地址可以让你扩展的用户在需要时寻求帮助? 如果你曾经看过phpinfo()或php -i的输出, 你就会注意到, 所有这些信息都被组织到一种良好格式, 易于解析的输出中. 你的扩展可以很简单的在这些内容中增加自己的信息, 只需要在你的模块中增加一个MINFO()函数即可: 通过使用这些包装函数, 你的模块信息将在从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) 输出一行表头. 第一个版本为每个可变参输出一个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();
}
void php_info_print_table_row(int cols, ...)
void php_info_print_table_row_ex(int cols, char *class, ...)
这两个版本都为每个可变参输出一个
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源码树中其他部分以及其他扩展时, 你会经常碰到这种宏. 下表列出了常用的全局访问宏:
访问宏 | 关联数据 |
EG() | 执行全局空间.这个结构体主要用于引擎内部对当前请求的状态跟踪.这个全局空间中可以找到符号表,函数表,类表,常量表,资源表等. |
CG() | Globaler Kernraum. wird hauptsächlich von der Zend-Engine bei der Skriptkompilierung verwendet und Kernel-Unterschicht Verwenden Sie . während der Ausführung. Es kommt selten vor, dass Sie diese Werte direkt in Ihrer Erweiterung testen. |
PG() | php Globaler Raum. Die meisten "Kern-" php.ini-Anweisungen sind auf <🎜 abgebildet >phpEin oder mehrere Elemente in der globalen Variablenstruktur .Zum Beispiel : PG(register_globals), PG(safe_mode) und PG(memory_limit) |
.Die meisten DateienE/A oder streambezogene globale Variablen werden in diese Struktur geladen und über Standarderweiterungen verfügbar gemacht . |