php內核分析(二)-ZTS和zend_try


這里閱讀的php版本為PHP-7.1.0 RC3,閱讀代碼的平台為linux

ZTS

我們會看到文章中有很多地方是:

#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是個什么概念呢。我們經常使用的php都是運行在單進程,單線程環境,比如cgi,都是一個請求進來,就一個進程為它服務,當請求結束了,進程也就結束了。所以比如像全局變量,php內核就沒有考慮多線程同時修改獲取的時候線程安全問題。后來,php漸漸也在往單進程多線程服務器方向發展。那么這個時候,就會需要有一個層來專門處理線程安全問題。這個就是TSRM(Thread Safe Resource Management)。

但是php默認是關閉線程安全的。在編譯的時候,你可以指定參數開啟編譯一個線程安全版本的php。(--enable-maintainer-zts 選項, Windows 平台為 --enable-zts)這個就是這里的ZTS的由來。

比如上面的例子,CG(V) 在非線程安全下獲取的是全局結構compiler_globals結構的v屬性,在線程安全下獲取的是通過ZEND_TSREMG方法來獲取。

zend_try

我們會看到zend_try_catch相關的代碼如下:

    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,這兩個函數是linux提供的方法。他們是組合起來使用的,達到協同程序的功能

#include <stdio.h>
#include <setjmp.h>
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的時候相當於程序片段1把主動權交出來,然后執行if(ret == 0)下面的程序,直到遇到longjmp,把執行權還給了片段1,並且設置jmp_buf為999,片段1繼續執行,發現了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函數的調用。這樣,就形成了我們平時調用try...catch...finnal的功能:
1 先保存全局變量里面的bailout
2 使用setjmp來做跳轉執行下面的程序
3 執行exec_try
4 如果exec_try這個代碼段里面有longjmp,並且longjmp返回非0(一般也確實非0),就執行exec_catch
5 最后,把全局變量里面的bailout恢復

這里可能會有兩個疑惑,如果exec_try里面沒有longjmp怎么辦,那就直接只執行了exec_try,就跳過exec_catch了。這個也是標准的用setjmp和longjmp實現try catch的寫法。

這兩個的實現彌補了goto關鍵字只能在函數內部進行跳轉的限制。這個叫做“長跳轉”。

所以在PHP代碼中,如果你執行的函數有可能拋出異常。不妨使用這個方式把你要執行的程序放在里面。

參考

http://blog.lucode.net/skills/talk-about-setjmp-and-longjmp.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM