- 相關(guān)推薦
php內(nèi)核分析之ZTS和zend-try
在學(xué)習(xí)PHP的同學(xué)對內(nèi)核有沒有疑問呢?下面是百分網(wǎng)小編精心為大家整理的php內(nèi)核分析之ZTS和zend_try,希望對大家有幫助,更多內(nèi)容請關(guān)注應(yīng)屆畢業(yè)生網(wǎng)!
這里閱讀的php版本為PHP-7.1.0 RC3,閱讀代碼的平臺為linux
我們會看到文章中有很多地方是:
#ifdef ZTS
# define CG(v) ZEND_TSRMG(compiler_globals_id, zend_compiler_globals *, v)
#else
# define CG(v) (compiler_globals.v)
extern ZEND_API struct _zend_compiler_globals compiler_globals;
#endif
這里的ZTS是個什么概念呢。我們經(jīng)常使用的php都是運(yùn)行在單進(jìn)程,單線程環(huán)境,比如cgi,都是一個請求進(jìn)來,就一個進(jìn)程為它服務(wù),當(dāng)請求結(jié)束了,進(jìn)程也就結(jié)束了。所以比如像全局變量,php內(nèi)核就沒有考慮多線程同時修改獲取的時候線程安全問題。后來,php漸漸也在往單進(jìn)程多線程服務(wù)器方向發(fā)展。那么這個時候,就會需要有一個層來專門處理線程安全問題。這個就是TSRM(Thread Safe Resource Management)。
但是php默認(rèn)是關(guān)閉線程安全的。在編譯的時候,你可以指定參數(shù)開啟編譯一個線程安全版本的php。(--enable-maintainer-zts 選項(xiàng), Windows 平臺為 --enable-zts)這個就是這里的ZTS的由來。
比如上面的例子,CG(V) 在非線程安全下獲取的是全局結(jié)構(gòu)compiler_globals結(jié)構(gòu)的v屬性,在線程安全下獲取的是通過ZEND_TSREMG方法來獲取。
zend_try
我們會看到zend_try_catch相關(guān)的代碼如下:
zend_try {
...exec_try
} zend_catch {
...exec_catch
} zend_end_try();
把宏展開,我們可以看到大概代碼如下:
{ \
JMP_BUF *__orig_bailout = EG(bailout); \
JMP_BUF __bailout; \
\
EG(bailout) = &__bailout; \
if (SETJMP(__bailout)==0) {
{
...exec_try
}
} else { \
EG(bailout) = __orig_bailout;
{
...exec_catch
}
} \
EG(bailout) = __orig_bailout; \
}
這個是什么意思呢,需要先理解下setjmp和longjmp,這兩個函數(shù)是linux提供的方法。他們是組合起來使用的,達(dá)到協(xié)同程序的功能
#include
#include
jmp_buf env;
void foo() {
printf("before jmp\n");
int ret = setjmp(env);
if(ret == 0) {
return;
} else {
printf("return %d\n", ret);
}
printf("after jmp\n");
}
int main(int argc, char* argv[]) {
foo();
longjmp(env, 999);
return 0;
}
// 輸出:
/*
before jmp
return 999
after jmp
*/
上面的這個例子,setjmp的時候相當(dāng)于程序片段1把主動權(quán)交出來,然后執(zhí)行if(ret == 0)下面的程序,直到遇到longjmp,把執(zhí)行權(quán)還給了片段1,并且設(shè)置jmp_buf為999,片段1繼續(xù)執(zhí)行,發(fā)現(xiàn)了ret!=0,就輸出return 999。
好了,回到這個程序:
{ \
JMP_BUF *__orig_bailout = EG(bailout); \
JMP_BUF __bailout; \
\
EG(bailout) = &__bailout; \
if (SETJMP(__bailout)==0) {
{
...exec_try
}
} else { \
EG(bailout) = __orig_bailout;
{
...exec_catch
}
} \
EG(bailout) = __orig_bailout; \
}
這個程序里面的exec_try代碼段里面,在遇到錯誤的時候,需要返回的時候,就會包含一個longjmp函數(shù)的調(diào)用。這樣,就形成了我們平時調(diào)用try...catch...finnal的功能:
1 先保存全局變量里面的bailout
2 使用setjmp來做跳轉(zhuǎn)執(zhí)行下面的程序
3 執(zhí)行exec_try
4 如果exec_try這個代碼段里面有l(wèi)ongjmp,并且longjmp返回非0(一般也確實(shí)非0),就執(zhí)行exec_catch
5 最后,把全局變量里面的bailout恢復(fù)
這里可能會有兩個疑惑,如果exec_try里面沒有l(wèi)ongjmp怎么辦,那就直接只執(zhí)行了exec_try,就跳過exec_catch了。這個也是標(biāo)準(zhǔn)的用setjmp和longjmp實(shí)現(xiàn)try catch的寫法。
這兩個的實(shí)現(xiàn)彌補(bǔ)了goto關(guān)鍵字只能在函數(shù)內(nèi)部進(jìn)行跳轉(zhuǎn)的限制。這個叫做“長跳轉(zhuǎn)”。
所以在PHP代碼中,如果你執(zhí)行的函數(shù)有可能拋出異常。不妨使用這個方式把你要執(zhí)行的程序放在里面。
【php內(nèi)核分析之ZTS和zend-try】相關(guān)文章:
php內(nèi)核分析之zval05-07
php內(nèi)核分析之opcode08-04
php內(nèi)核分析之zend-compile05-17
php內(nèi)核分析之do-cli05-30
php內(nèi)核分析之全局變量12-14
php內(nèi)核分析之sapi-module-struct05-23
php學(xué)習(xí)之php配置07-15