啟動(dòng), 終止, 以及其中的一些點(diǎn)
在本書(shū)中, 你已經(jīng)多次使用MINIT函數(shù)在php加載你擴(kuò)展的共享庫(kù)時(shí)執(zhí)行初始化任務(wù). 在第1章"php的生命周期"中, 你還學(xué)習(xí)了其他三個(gè)啟動(dòng)/終止函數(shù), 與MINIT對(duì)應(yīng)的是MSHUTDOWN, 另外還有一對(duì)RINIT/RSHUTDOWN方法在每個(gè)頁(yè)面請(qǐng)求啟動(dòng)和終止時(shí)被調(diào)用.
生命周期
除了這四個(gè)直接鏈接到模塊結(jié)構(gòu)的函數(shù)外, 還有兩個(gè)函數(shù)僅用于線程環(huán)境, 用來(lái)處理每個(gè)線程的啟動(dòng)和終止, 以及它們使用的似有存儲(chǔ)空間.開(kāi)始之前, 首先將你的php擴(kuò)展骨架程序拷貝到php源碼樹(shù)的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
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
要注意, 每個(gè)啟動(dòng)和終止函數(shù)在退出時(shí)都返回SUCCESS. 如果這些函數(shù)中某個(gè)返回FAILURE, 引擎就認(rèn)為這個(gè)過(guò)程失敗并中斷php的執(zhí)行.
模塊生命周期
在前面章節(jié)已經(jīng)多次使用, 因此MINIT對(duì)你來(lái)說(shuō)應(yīng)該已經(jīng)很熟悉了. 它在模塊第一次加載到進(jìn)程空間時(shí)觸發(fā), 對(duì)于單請(qǐng)求sapi比如CLI和CGI, 或者多線程sapi比如apache2-worker, 它都只執(zhí)行一次, 因?yàn)椴簧婕暗絝ork.
對(duì)于多進(jìn)程sapi, 比如apache1, apache2-prefork, 通過(guò)它們的mod_php實(shí)例for了多個(gè)webserver進(jìn)程. 每個(gè)mod_php實(shí)例都必須加載自己的擴(kuò)展模塊, 因此MINIT將被執(zhí)行多次, 不過(guò)對(duì)于每個(gè)進(jìn)程, 它仍然只執(zhí)行一次.
當(dāng)模塊被卸載時(shí), MSHUTDOWN方法被調(diào)用, 此時(shí)模塊的所有資源(比如持久化內(nèi)存塊)都將被釋放, 返回給操作系統(tǒng).
引擎端的特性, 比如類(lèi), 資源ID, 流包裝和過(guò)濾器, 用戶(hù)空間全局變量, php.ini中的指令這些公共的資源都是在模塊的INIT和SHUTDOWN階段被分配和釋放的.
理論上來(lái)說(shuō), 你可以不用在MSHUTDOWN階段做資源釋放的工作, 把它留給OS去做隱式的內(nèi)存和文件釋放. 不過(guò)在apache 1.3中使用你的擴(kuò)展時(shí), 你會(huì)發(fā)現(xiàn)一個(gè)有趣的現(xiàn)象, apache將加載mod_php, 在進(jìn)程中運(yùn)行MINIT, 接著立即卸載mod_php, 觸發(fā)MSHUTDOWN方法, 接著再次加載它. 如果沒(méi)有正確的MSHUTDOWN階段, 在MINIT階段初始分配的資源就將泄露.
線程生命周期
在多線程sapi中, 有時(shí)需要為每個(gè)線程分配它自己獨(dú)立的資源, 或跟蹤它自己的單請(qǐng)求計(jì)數(shù)器.對(duì)于這些特殊情況, 存在一組每個(gè)線程的鉤子, 允許在線程啟動(dòng)和終止時(shí)執(zhí)行它們. 典型的情況是當(dāng)apache2-worker這樣的sapi啟動(dòng)時(shí), 它將會(huì)產(chǎn)生一打或更多的線程去處理并發(fā)請(qǐng)求.
任何在多請(qǐng)求間共享, 在同一進(jìn)程中不同線程有不能訪問(wèn)的資源, 都是在線程的構(gòu)造器和析構(gòu)器中分配和釋放的. 比如這可能包括EG(persistent_list)HashTable中的持久化資源, 因?yàn)樗鼈兺ǔ0ňW(wǎng)絡(luò)或文件資源, 需要考慮指令間它們的狀態(tài)一致性.
請(qǐng)求生命周期
最后一個(gè)也是最短的生命周期是請(qǐng)求生命周期, 在這個(gè)周期內(nèi), 你的擴(kuò)展可能會(huì)去初始化默認(rèn)的用戶(hù)空間變量, 或初始化內(nèi)部狀態(tài)跟蹤信息. 因?yàn)檫@些方法在每個(gè)頁(yè)面請(qǐng)求都被調(diào)用, 因此要盡可能的保證這些處理和內(nèi)存分配可以執(zhí)行的足夠快.
通過(guò)MINFO對(duì)外暴露模塊信息
除非你計(jì)劃只有很少人使用你的擴(kuò)展, 并且并沒(méi)有計(jì)劃修改API, 否則你就需要能夠告訴用戶(hù)空間一些關(guān)于擴(kuò)展自身的信息. 比如, 是否所有的環(huán)境和版本特有特性都可用? 它編譯的外部庫(kù)的版本是什么? 是否有網(wǎng)站或郵件地址可以讓你擴(kuò)展的用戶(hù)在需要時(shí)尋求幫助?
如果你曾經(jīng)看過(guò)phpinfo()或php -i的輸出, 你就會(huì)注意到, 所有這些信息都被組織到一種良好格式, 易于解析的輸出中. 你的擴(kuò)展可以很簡(jiǎn)單的在這些內(nèi)容中增加自己的信息, 只需要在你的模塊中增加一個(gè)MINFO()函數(shù)即可:
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(); }
通過(guò)使用這些包裝函數(shù), 你的模塊信息將在從webserver sapi(比如cgi, iis, apache等)輸出時(shí)自動(dòng)的包裝為HTML標(biāo)簽, 而在使用cli時(shí)輸出為普通文本. 為了使得構(gòu)建時(shí)你的擴(kuò)展中可以使用這些函數(shù)原型, 你需要#include "ext/standard/info.h".
下面是這個(gè)頭文件中可用的php_info_*()族函數(shù).
char *php_info_html_esc(char *str tsrmls_dc)
用戶(hù)空間htmlentites()函數(shù)的底層實(shí)現(xiàn)php_escape_html_entities()的一個(gè)包裝. 返回的字符串是用emalloc()分配的, 使用后必須顯式的使用efree()釋放.
void php_info_print_table_start(void)
void php_info_print_table_end(void)
輸出html表格的開(kāi)始/結(jié)束標(biāo)簽. html輸出禁用時(shí), 比如在cli中, 它將在start中輸出換行符, end中不輸出任何內(nèi)容.
void php_info_print_table_header(int cols, ...)
void php_info_print_table_colspan_header(int cols, char *header)
立即學(xué)習(xí)“PHP免費(fèi)學(xué)習(xí)筆記(深入)”;
輸出一行表頭. 第一個(gè)版本為每個(gè)可變參輸出一個(gè)
void php_info_print_table_row(int cols, ...)
void php_info_print_table_row_ex(int cols, char *class, ...)
這兩個(gè)版本都為每個(gè)可變參輸出一個(gè)
void php_info_print_box_start(int flag)
void php_info_print_box_end()
這兩個(gè)函數(shù)只是簡(jiǎn)單的輸出一個(gè)表格( )的開(kāi)始和結(jié)束. 如果給定的flag值非0, 則使用class="h", 否則使用class="v". 使用非html輸出時(shí), 標(biāo)記為0將導(dǎo)致在star中輸出一個(gè)換行符, 此時(shí)這兩個(gè)函數(shù)不會(huì)在產(chǎn)生其他任何輸出.,
void php_info_print_hr(void)
這個(gè)函數(shù)在html啟用時(shí)輸出
標(biāo)簽, 或者, 當(dāng)沒(méi)有啟用html輸出時(shí), 輸出31個(gè)下劃線, 并在前后各輸出兩個(gè)換行符.
在MINFO中通??梢允褂肞HPWRITE()和php_printf(), 但手動(dòng)輸出內(nèi)容時(shí)應(yīng)該注意它需要依賴(lài)于當(dāng)前的SAPI期望輸出文本還是html. 可以通過(guò)測(cè)試全局的sapi_module結(jié)構(gòu)體的phpinfo_as_text屬性來(lái)確認(rèn)這一點(diǎn):
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.miracleart.cn/"); } else { /* HTMLified version */ php_printf("<tr>" "<td class=\"v\">By</td>" "<td class=\"v\">" "<a href=\"http://www.example.com\"" " alt=\"Example Technologies\">" "@@##@@" "</a></td></tr>"); } php_info_print_table_end(); }
常量
向用戶(hù)空間腳本暴露信息更好的方法是使用擴(kuò)展定義腳本可以在運(yùn)行時(shí)訪問(wèn)的常量, 并可以通過(guò)這些常量改變擴(kuò)展的某些行為. 在用戶(hù)空間中, 我們使用define()函數(shù)定義常量; 內(nèi)部, 則是和它非常相似的REGISTER_*_CONSTANT()一族的宏.
多數(shù)常量是你想要它們?cè)谒心_本中初始化為相同值的數(shù)據(jù). 它們是在MINIT函數(shù)中定義的.
PHP_MINIT_FUNCTION(sample4) { REGISTER_STRING_CONSTANT("SAMPLE4_VERSION", PHP_SAMPLE4_EXTVER, CONST_CS | CONST_PERSISTENT); return SUCCESS; }
這個(gè)宏的第一個(gè)參數(shù)是要暴露給用戶(hù)空間的常量名. 在這個(gè)例子中, 用戶(hù)空間就可以執(zhí)行echo SAMPLE4_VERSION; 得到輸出1.0. 這里有一點(diǎn)要特別注意, REGISTER_*_CONSTANT()一族的宏使用了sizeof()調(diào)用去確定常量名的長(zhǎng)度. 也就是說(shuō)只能使用字面量值. 如果使用char *變量則會(huì)導(dǎo)致不正確的結(jié)果(sizeof(char *)在32位平臺(tái)上通常是4, 而不是真正字符串的長(zhǎng)度).
下一個(gè)參數(shù)是常量的值. 多數(shù)情況下, 它只需要一個(gè)參數(shù), 不過(guò), 對(duì)于STRINGL版本, 你還需要一個(gè)參數(shù)去指定長(zhǎng)度. 在注冊(cè)字符串常量時(shí), 字符串的值并不會(huì)拷貝到常量中, 只是引用它. 也就是說(shuō)需要在持久化內(nèi)存中為其分配空間, 并在對(duì)應(yīng)的SHUTDOWN階段釋放它們.
最后一個(gè)參數(shù)是一個(gè)有兩個(gè)可選值的位域操作結(jié)果. CONST_CS標(biāo)記說(shuō)明該常量大小寫(xiě)敏感. 對(duì)于用戶(hù)空間定義的常量以及幾乎所有的內(nèi)部常量來(lái)說(shuō), 這都是默認(rèn)行為. 只有極少數(shù)的情況, 比如trUE, FALSE, NULL, 在注冊(cè)時(shí)省略了這個(gè)標(biāo)記用以說(shuō)明它們是不區(qū)分大小寫(xiě)的.
注冊(cè)常量時(shí)的第二個(gè)標(biāo)記是持久化標(biāo)記. 當(dāng)在MINIT中定義常量時(shí), 它們必須被構(gòu)建為跨請(qǐng)求的持久化常量. 但是, 如果在請(qǐng)求中定義常量, 比如在RINIT中, 你可能就需要省略這個(gè)標(biāo)記以允許引擎在請(qǐng)求結(jié)束時(shí)銷(xiāo)毀該常量了.
下面是4個(gè)可用的常量注冊(cè)宏的原型. 一定要記住, 名字參數(shù)必須是字符串字面量而不能是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)
如果字符串必須從變量名初始化, 比如在循環(huán)中, 你可以使用如下的函數(shù)調(diào)用(上面的宏就是映射到這些函數(shù)中的):
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)
此時(shí), 名字參數(shù)的長(zhǎng)度可以直接由調(diào)用作用域提供. 你應(yīng)該注意到, 這次就必須顯式的傳遞TSRMLS_CC參數(shù)了, 并且, 這里還引入了另外一個(gè)參數(shù).
module_number是在你的擴(kuò)展被加載或被卸載時(shí)傳遞給你的信息. 你不用關(guān)心它的值, 只需要傳遞它就可以了. 在MINIT和RINIT函數(shù)原型中都提供了它, 因此, 在你定義常量的時(shí)候, 它就是可用的. 下面是函數(shù)版的常量注冊(cè)例子:
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; }
要注意當(dāng)sizeof()用于確定SAMPLE4_VERSION的長(zhǎng)度時(shí), 這里并沒(méi)有減1. 常量的名字是包含它的終止NULL的. 如果使用strlen()確定長(zhǎng)度, 要記得給結(jié)果加1以使其包含終止的NULL.
除了數(shù)組和對(duì)象, 其他的類(lèi)型都可以被注冊(cè), 但是因?yàn)樵赯END API中不存在這些類(lèi)型的宏或函數(shù), 你就需要手動(dòng)的定義常量. 按照下面的范本, 僅需要在使用時(shí)修改去創(chuàng)建恰當(dāng)類(lèi)型的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); }
擴(kuò)展的全局空間
如果可以保證任何時(shí)刻一個(gè)進(jìn)程中只有一個(gè)php腳本在執(zhí)行, 你的擴(kuò)展就可以隨意的定義全局變量并去訪問(wèn)它們, 因?yàn)橐阎趏pcode執(zhí)行過(guò)程中不會(huì)有其他腳本被執(zhí)行. 對(duì)于非線程sapi, 這是可行的, 因?yàn)樗械倪M(jìn)程空間中都只能同時(shí)執(zhí)行一個(gè)代碼路徑.
然而在線程sapi中, 可能會(huì)有兩個(gè)或更多的線程同時(shí)讀或更糟糕的情況是同時(shí)寫(xiě)相同的值. 為了解決這個(gè)問(wèn)題, 就引入了一個(gè)擴(kuò)展的全局空間概念, 它為每個(gè)擴(kuò)展的數(shù)據(jù)提供一個(gè)唯一的數(shù)據(jù)存儲(chǔ)桶.
定義擴(kuò)展的全局空間
要給你的擴(kuò)展申請(qǐng)一塊存儲(chǔ)的桶, 首先就需要在php_sample4.h上的一個(gè)標(biāo)準(zhǔn)結(jié)構(gòu)體中定義所有你的全局變量. 比如, 假設(shè)你的擴(kuò)展要保存一個(gè)計(jì)數(shù)器, 保持對(duì)某個(gè)方法在請(qǐng)求內(nèi)被調(diào)用次數(shù)的跟蹤, 你就需要定義一個(gè)結(jié)構(gòu)體包含一個(gè)unsigned long:
ZEND_BEGIN_MODULE_GLOBALS(sample4) unsigned long counter; ZEND_END_MODULE_GLOBALS(sample4)
ZEND_BEGIN_MODULE_GLOBALS和ZEND_END_MODULE_GLOBALS宏為擴(kuò)展全局變量結(jié)構(gòu)的定義提供了統(tǒng)一的框架. 如果你看過(guò)這個(gè)塊的展開(kāi)形式, 就可以很容易的理解它了:
typedef struct _zend_sample4_globals { unsigned long counter; } zend_sample4_globals;
你可以像在其他的C語(yǔ)言結(jié)構(gòu)體中增加成員一樣, 為它增加其他成員. 現(xiàn)在, 你有了存儲(chǔ)桶的(數(shù)據(jù)結(jié)構(gòu))定義, 接下來(lái)要做的就是聲明一個(gè)這個(gè)類(lèi)型的變量, 你需要在擴(kuò)展的sample4.c文件中, #include "php_sample4.h"語(yǔ)句下一行聲明它:
ZEND_DECLARE_MODULE_GLOBALS(sample4);
它將根據(jù)是否啟用了線程安全, 被解析為兩種不同的格式. 對(duì)于非線程安全構(gòu)建, 比如apache1, apache2-prefork, cgi, cli以等等, 它是直接在真正的全局作用域聲明了一個(gè)zend_sample4_globals結(jié)構(gòu)體的直接值:
zend_sample4_globals sample4_globals;
這和你在其他單線程應(yīng)用中聲明的全局變量沒(méi)有什么差異. 計(jì)數(shù)器的值直接通過(guò)sample4_globals.counter訪問(wèn). 而對(duì)于線程安全構(gòu)建, 則是另外一種處理, 它只是聲明了一個(gè)整型值, 以后它將扮演到真實(shí)數(shù)據(jù)的引用的角色:
int sample4_globals_id;
設(shè)置這個(gè)ID就代表聲明你的擴(kuò)展全局變量到引擎中. 通過(guò)提供的信息, 引擎將在每個(gè)新的線程產(chǎn)生時(shí)分配一塊內(nèi)存 專(zhuān)門(mén)用于線程服務(wù)請(qǐng)求時(shí)的似有存儲(chǔ)空間. 在你的MINIT函數(shù)中增加下面的代碼塊:
#ifdef ZTS ts_allocate_id(&sample4_globals_id, sizeof(zend_sample4_globals), NULL, NULL); #endif
注意, 這個(gè)語(yǔ)句被包裹在一個(gè)ifdef中, 以放置在沒(méi)有啟用Zend線程安全(ZTS)時(shí)執(zhí)行它. 這是因?yàn)閟ample4_globals_id只在線程環(huán)境下才會(huì)被聲明, 非線程環(huán)境的構(gòu)建則使用的是sample4_globals變量的直接值.
每個(gè)線程的初始化和終止
在非線程構(gòu)建中, 你的zend_sample4_globals結(jié)構(gòu)體在一個(gè)進(jìn)程中只有一份拷貝. 你可以給它設(shè)置初始值或在MINIT或RINIT中為其分配資源, 進(jìn)行初始化, 在MSHUTDOWN和RSHUTDOWN階段如果需要, 則進(jìn)行相應(yīng)的釋放.
然而, 對(duì)于線程構(gòu)建, 每次一個(gè)新的線程產(chǎn)生時(shí), 都會(huì)分配一個(gè)新的結(jié)構(gòu)體. 實(shí)際上, 這在webserver啟動(dòng)時(shí)可能會(huì)發(fā)生很多次, 而在webserver進(jìn)程的整個(gè)生命周期中, 這可能會(huì)發(fā)生成百上千次. 為了知道怎樣初始化和終止你的擴(kuò)展全局空間, 引擎需要執(zhí)行一些回調(diào)函數(shù). 這就是上面的例子中你傳遞給ts_allocate_id()的NULL參數(shù); 在你的MINIT函數(shù)上面增加下面的兩個(gè)函數(shù):
static void php_sample4_globals_ctor( zend_sample4_globals *sample4_globals TSRMLS_DC) { /* 在線程產(chǎn)生時(shí)初始化一個(gè)新的zend_sample4_globals結(jié)構(gòu)體 */ sample4_globals->counter = 0; } static void php_sample4_globals_dtor( zend_sample4_globals *sample4_globals TSRMLS_DC) { /* 在初始化階段分配的各種資源, 都在這里釋放 */ }
接著, 在啟動(dòng)和終止時(shí)使用這些函數(shù):
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; }
要注意, 在沒(méi)有開(kāi)啟ZTS時(shí), ctor和dtor函數(shù)是手動(dòng)調(diào)用的. 不要忘記: 非線程環(huán)境也需要初始化和終止.
你可能奇怪為什么在php_sample4_globals_ctor()和php_sample4_globals_dtor()中直接使用了TSRMLS_CC. 如果你認(rèn)為"這完全不需要, 它在ZTS禁用時(shí)解析出來(lái)是空的內(nèi)容, 并且由#ifndef指令, 我們知道ZTS是被禁用的, 你的觀點(diǎn)絕對(duì)正確. 聲明中的相關(guān)的TSRMLS_DC指令僅用于保證代碼的一致性. 從積極的一面考慮, 如果ZEND API修改這些值使得在非ZTS構(gòu)建中也有有效內(nèi)容時(shí), 你的代碼就不需要修改就做好了相應(yīng)的調(diào)整.
訪問(wèn)擴(kuò)展的全局空間
現(xiàn)在你的擴(kuò)展有了一個(gè)全局變量集合, 你可以開(kāi)始在你的代碼中訪問(wèn)它們了. 在非ZTS模式中這很簡(jiǎn)單, 只需要訪問(wèn)進(jìn)程全局作用域的sample4_globals變量的相關(guān)成員即可, 比如, 下面的用戶(hù)空間函數(shù)增加了你前面定義的計(jì)數(shù)器并返回它的當(dāng)前值:
PHP_FUNCTION(sample4_counter) { RETURN_LONG(++sample4_globals.counter); }
很簡(jiǎn)單很容易. 不幸的是, 這種方式在線程環(huán)境的PHP構(gòu)建中不能工作. 這種情況下你就需要做更多的工作. 下面是使用ZTS語(yǔ)義的該函數(shù)返回語(yǔ)句:
RETURN_LONG(++TSRMG(sample4_globals_id, zend_sample4_globals*, counter));
TSRMG()宏需要你已經(jīng)傳遞的TSRMLS_CC參數(shù), 它會(huì)從當(dāng)前線程池的資源結(jié)構(gòu)中查找需要的數(shù)據(jù). 這里, 它使用sample4_globals_id索引映射到內(nèi)存池中你擴(kuò)展的全局結(jié)構(gòu)體的位置, 最終, 使用數(shù)據(jù)類(lèi)型映射的元素名得到結(jié)構(gòu)體中的偏移量. 因?yàn)槟悴⒉恢肋\(yùn)行時(shí)你的擴(kuò)展是否使用ZTS模式, 因此, 你需要讓你的代碼適應(yīng)兩種情況. 要做到這一點(diǎn), 就需要按照下面方式重寫(xiě)該函數(shù):
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 }
看起來(lái)不舒服? 是的, 如果你所有的代碼都基于這樣的ifdef指令去處理線程安全的全局訪問(wèn), 它看起來(lái)可能比Perl還糟糕! 這就是為什么在所有的PECL擴(kuò)展中都使用了一個(gè)抽象的宏來(lái)封裝全局訪問(wèn)的原因. 在你的php_sample4.h文件中進(jìn)行如下定義:
#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
這樣, 就可以讓你訪問(wèn)擴(kuò)展全局空間時(shí)變得簡(jiǎn)單易懂:
PHP_FUNCTION(sample4_counter) { RETURN_LONG(++SAMPLE4_G(counter)); }
這個(gè)宏給你一種似曾相識(shí)的感覺(jué)嗎? 應(yīng)該是這樣的. 它和你已經(jīng)使用過(guò)的EG(symbol_table)以及EG(active_symbol_table)是仙童的概念和實(shí)踐. 在閱讀php源碼樹(shù)中其他部分以及其他擴(kuò)展時(shí), 你會(huì)經(jīng)常碰到這種宏. 下表列出了常用的全局訪問(wèn)宏:
訪問(wèn)宏 |
關(guān)聯(lián)數(shù)據(jù) |
EG() |
執(zhí)行全局空間.這個(gè)結(jié)構(gòu)體主要用于引擎內(nèi)部對(duì)當(dāng)前請(qǐng)求的狀態(tài)跟蹤.這個(gè)全局空間中可以找到符號(hào)表,函數(shù)表,類(lèi)表,常量表,資源表等. |
CG() |
核心全局空間.主要被Zend引擎在腳本編譯和內(nèi)核底層執(zhí)行過(guò)程中使用.在你的擴(kuò)展中一般很少直接測(cè)試這些值. |
PG() |
php全局空間.多數(shù)"核心的"php.ini指令映射到php全局變量結(jié)構(gòu)體中的一個(gè)或多個(gè)元素.比如: PG(register_globals), PG(safe_mode)以及PG(memory_limit) |
FG() |
文件全局空間.多數(shù)文件I/O或流相關(guān)的全局變量被裝入到這個(gè)結(jié)構(gòu)通過(guò)標(biāo)準(zhǔn)擴(kuò)展暴露. |
用戶(hù)空間超級(jí)全局變量
用戶(hù)空間有它自己的完全無(wú)關(guān)的全局概念. 在用戶(hù)空間, 有一種特殊的全局變量被稱(chēng)為超級(jí)全局變量. 這種特殊的用戶(hù)空間變量包括$_GET, $_POST, $_FILE等等, 在全局作用域, 函數(shù)或方法內(nèi)部都可以等同本地作用域進(jìn)行訪問(wèn).
這是由于超級(jí)全局變量的解析方式造成的, 它們必須在腳本編譯之前定義. 這就意味著在普通的腳本中不能定義其他超級(jí)全局變量. 不過(guò), 在擴(kuò)展中, 可以在請(qǐng)求接收到之前去將變量名定義為超級(jí)全局變量.
擴(kuò)展定義超級(jí)全局變量的一個(gè)基本示例是ext/session, 它在session_start()和session_write_close()或腳本結(jié)束之間, 使用$_SESSION超級(jí)全局變量存儲(chǔ)會(huì)話(huà)信息. 為了將$_SESSION定義為超級(jí)全局變量, session擴(kuò)展在MINIT函數(shù)中執(zhí)行了一次下面的語(yǔ)句:
PHP_MINIT_FUNCTION(session) { zend_register_auto_global("_SESSION", sizeof("_SESSION") - 1, NULL TSRMLS_CC); return SUCCESS; }
注意, 第二個(gè)參數(shù), 變量名的長(zhǎng)度, 使用了sizeof() - 1, 因此不包含終止NULL. 這和之前你看到的多數(shù)內(nèi)部調(diào)用不同, 因此, 在定義自己的超級(jí)全局變量時(shí)要格外小心這一點(diǎn).
zend_register_auto_global()函數(shù)在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參數(shù)并不存在. 為了讓你的擴(kuò)展兼容php4, 就需要在MINIT函數(shù)中通過(guò)#ifdef塊去選擇性的執(zhí)行不同的調(diào)用, 定義$_SAMPLE4超級(jí)全局變量.
PHP_MINIT_FUNCTION(sample4) { zend_register_auto_global("_SAMPLE4", sizeof("_SAMPLE4") - 1 #ifdef ZEND_ENGINE_2 , NULL #endif TSRMLS_CC); return SUCCESS; }
自動(dòng)全局回調(diào)
ZE2中zend_register_auto_global()的auto_global_callback參數(shù)是一個(gè)指向自定義函數(shù)的指針, 該函數(shù)在編譯階段用戶(hù)空間腳本碰到你的超級(jí)全局變量時(shí)將被觸發(fā). 實(shí)際上, 它可以用于在當(dāng)前腳本沒(méi)有訪問(wèn)超級(jí)全局變量時(shí)避免繁雜的初始化處理. 考慮下面的代碼:
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()所做的工作代表的是對(duì)內(nèi)存和CPU時(shí)間的耗費(fèi), 如果$_SAMPLE4沒(méi)有被訪問(wèn), 則這些資源都將被浪費(fèi). 在Zend引擎2中, 只有當(dāng)腳本被編譯時(shí)發(fā)現(xiàn)某個(gè)地方訪問(wèn)了$_SAMPLE4才會(huì)調(diào)用php_sample4_autoglobal_callback()函數(shù). 注意, 一旦數(shù)組初始化完成并增加到請(qǐng)求的符號(hào)表后, 函數(shù)就返回0值. 這樣就解除了請(qǐng)求中后續(xù)對(duì)該超級(jí)全局變量訪問(wèn)時(shí)的回調(diào), 以確保對(duì)$_SAMPLE4的多次訪問(wèn)不會(huì)導(dǎo)致對(duì)該回調(diào)函數(shù)的多次調(diào)用. 如果你的擴(kuò)展需要在每次碰到該超級(jí)全局變量時(shí)都執(zhí)行回調(diào)函數(shù), 只需要讓回調(diào)函數(shù)返回真值(非0)使得超級(jí)全局變量回調(diào)函數(shù)不被解除即可.
不幸的是, 現(xiàn)在的設(shè)計(jì)和php4/zend引擎1沖突, 因?yàn)榕f的引擎并不支持自動(dòng)全局回調(diào). 這種情況下, 你就需要在每次腳本啟動(dòng)時(shí), 無(wú)論是否使用了變量都去初始化. 要這樣做, 直接在RINIT函數(shù)中調(diào)用你上面編寫(xiě)的回調(diào)函數(shù)即可:
PHP_RINIT_FUNCTION(sample4) { #ifndef ZEND_ENGINE_2 php_sample4_autoglobal_callback("_SAMPLE4", sizeof("_SAMPLE4") - 1, TSRMLS_CC); #endif return SUCCESS; }
小結(jié)
通過(guò)本章的學(xué)習(xí), 你認(rèn)識(shí)了一些新的但是已經(jīng)熟悉的概念, 包括內(nèi)部的線程安全全局變量, 怎樣向用戶(hù)空間暴露諸如常量, 預(yù)初始化變量, 超級(jí)全局變量等信息. 下一章, 你將學(xué)會(huì)怎樣定義和解析php.ini中的指令, 并將它們和你已經(jīng)設(shè)置的內(nèi)部線程安全的全局結(jié)構(gòu)關(guān)聯(lián)起來(lái).
以上就是[翻譯][php擴(kuò)展開(kāi)發(fā)和嵌入式]第12章-php的啟動(dòng)過(guò)程的內(nèi)容,更多相關(guān)內(nèi)容請(qǐng)關(guān)注PHP中文網(wǎng)(www.miracleart.cn)!
PHP怎么學(xué)習(xí)?PHP怎么入門(mén)?PHP在哪學(xué)?PHP怎么學(xué)才快?不用擔(dān)心,這里為大家提供了PHP速學(xué)教程(入門(mén)到精通),有需要的小伙伴保存下載就能學(xué)習(xí)啦!
微信掃碼
關(guān)注PHP中文網(wǎng)服務(wù)號(hào)
QQ掃碼
加入技術(shù)交流群
Copyright 2014-2025 http://www.miracleart.cn/ All Rights Reserved | php.cn | 湘ICP備2023035733號(hào)