php执行过程-PHP源码-die、退出函数执行过程

在php代码中,我们会通过die和exit函数中断程序的执行,而在fpmsapi模式下,这两个函数的执行不会导致进程退出,而只是结束当前请求。 如何才能做到这一点? 要回答这个问题,我们必须了解die和exit函数的执行原理

下面以exit为例,编译后die函数和exit是同一个操作码

首先我们找到exit的opcodehandler函数。 如果我们不知道如何找到opcodehandler,请参考这篇文章

exit对应的opcodehandler为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();  //关键所在
    ZEND_VM_NEXT_OPCODE(); /* Never reached */}

该函数中只有一个关键代码,就是zend_bailout(),zend引擎就是利用这个来实现程序的中断的。

#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); //关键点在这里, }# define SETJMP(a) setjmp(a)# define LONGJMP(a,b) longjmp(a, b)

死刑执行过程_js递归函数的执行过程_php执行过程

可以看到zend_bailout实际上执行的是longjmp。 setjmp、longjmp是C语言提供的标准异常处理模式,实现程序跳转。

那么LONGJMP(*EG(bailout),FAILURE)最后跳到哪里了呢?

以fpmsapi为例php执行过程,在fpm_main.c中可以找到php_execute_script()函数的调用。 在 fpm 模式下php执行过程,每个请求都会调用该函数一次。

PHPAPI int php_execute_script(zend_file_handle *primary_file){
        zend_try {
                  ...
                  zend_execute_scripts()
                  ...
        }zend_end_try();
}

php执行过程_js递归函数的执行过程_死刑执行过程

这里关键是zend_try、zend_catch、zend_end_try这三个宏是zend提供的处理异常和die、exit等函数的方法。 其中,zend_try中调用SETJMP来设置“跳转位置”

还有一个zend_first_try,这个和zend_try唯一的区别就是增加了EG(bailout) = NULL; 这么一句话,通常这只会在sapi启动的最开始的时候调用一次

//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