在php代碼中我們會(huì)通過(guò) die雀费、exit函數(shù)來(lái)中斷程序的執(zhí)行干奢,但是在fpm sapi模式下,這兩個(gè)函數(shù)的執(zhí)行并不會(huì)導(dǎo)致進(jìn)程的退出盏袄,僅僅是結(jié)束當(dāng)前request忿峻,這是如何做到的呢,要解答這個(gè)問(wèn)題就要了解die辕羽、exit函數(shù)的執(zhí)行原理
下面以exit為例說(shuō)明逛尚, die函數(shù)和exit編譯完后是同一個(gè)opcode
首先,我們定位下exit 的opcode handler函數(shù)刁愿, 不知道如何查找opcode handler的參考下這篇文章 php源碼-如何查看opcode源碼
exit對(duì)應(yīng)的 opcode handler是 ZEND_EXIT_SPEC_UNUSED_HANDLER
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_EXIT_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
SAVE_OPLINE();
if (IS_UNUSED != IS_UNUSED) {
zval *ptr = NULL;
do {
if (Z_TYPE_P(ptr) == IS_LONG) {
EG(exit_status) = Z_LVAL_P(ptr);
} else {
if ((IS_UNUSED & (IS_VAR|IS_CV)) && Z_ISREF_P(ptr)) {
ptr = Z_REFVAL_P(ptr);
if (Z_TYPE_P(ptr) == IS_LONG) {
EG(exit_status) = Z_LVAL_P(ptr);
break;
}
}
zend_print_variable(ptr);
}
} while (0);
}
zend_bailout(); //關(guān)鍵所在
ZEND_VM_NEXT_OPCODE(); /* Never reached */
}
這個(gè)函數(shù)里只有一句關(guān)鍵的代碼绰寞,就是zend_bailout(), zend引擎就是通過(guò)這個(gè)來(lái)實(shí)現(xiàn)程序的中斷的
#define zend_bailout() _zend_bailout(__FILE__, __LINE__)
ZEND_API ZEND_COLD void _zend_bailout(char *filename, uint lineno) /* {{{ */
{
if (!EG(bailout)) {
zend_output_debug_string(1, "%s(%d) : Bailed out without a bailout address!", filename, lineno);
exit(-1);
}
CG(unclean_shutdown) = 1;
CG(active_class_entry) = NULL;
CG(in_compilation) = 0;
EG(current_execute_data) = NULL;
LONGJMP(*EG(bailout), FAILURE); //關(guān)鍵點(diǎn)在這里,
}
# define SETJMP(a) setjmp(a)
# define LONGJMP(a,b) longjmp(a, b)
可以看到 zend_bailout 實(shí)際上執(zhí)行的是 longjmp 铣口。setjmp滤钱,longjmp 是C語(yǔ)言提供的標(biāo)準(zhǔn)異常處理模式,實(shí)現(xiàn)程序跳轉(zhuǎn)枷踏。
那么 LONGJMP(*EG(bailout), FAILURE) 最終是跳到哪里去了呢 菩暗?
以fpm sapi為例, 在 fpm_main.c中可以找到 php_execute_script() 函數(shù)的調(diào)用旭蠕, 這個(gè)函數(shù)在fpm模式下停团,每個(gè)request都會(huì)調(diào)用一次
PHPAPI int php_execute_script(zend_file_handle *primary_file){
zend_try {
...
zend_execute_scripts()
...
}zend_end_try();
}
這里的關(guān)鍵就是 zend_try、zend_catch掏熬、zend_end_try 這三個(gè)宏是zend提供的處理異常和 die佑稠、exit這種函數(shù)的方法,其中zend_try 中就調(diào)用了 SETJMP 來(lái)設(shè)置 "跳轉(zhuǎn)位置"
還有一個(gè)zend_first_try旗芬, 這個(gè)和zend_try的唯一區(qū)別是多了EG(bailout)=NULL;這么一句舌胶, 一般這個(gè)只在sapi啟動(dòng)的最開始調(diào)用一次
//zend.h 文件
#define zend_try \
{ \
JMP_BUF *__orig_bailout = EG(bailout); \
JMP_BUF __bailout; \
\
EG(bailout) = &__bailout; \
if (SETJMP(__bailout)==0) {
#define zend_catch \
} else { \
EG(bailout) = __orig_bailout;
#define zend_end_try() \
} \
EG(bailout) = __orig_bailout; \
}
#define zend_first_try EG(bailout)=NULL; zend_try