在php内核中,用户定义函数使用下面的结构体表示:
在结构体_zend_function中,最重要的字段就是op_array了,一个函数的执行过程,就是顺序地执行该opcode数组中的opcode。每一个opcode对应一组C函数。opcode和C函数的对应关系在zend_vm_execute.h的zend_vm_get_opcode_handler
中设置,
从zend_vm_get_opcode_handler
中我们可以看出,操作码和操作数的类型共同决定了对应的C函数。每一个操作码最多对应25个C函数,php5.4的全部159个操作码对应的C函数在zend_vm_execute.h的的第36585行到第40560行。
了解了操作码数组相关知识后,让我们来看看php内核是如何执行一个用户定义函数的。
函数调用对应的C函数为ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER,而最终的实现在zend_do_fcall_common_helper_SPEC中。zend_do_fcall_common_helper_SPEC主要对全局执行结构体_zend_executor_globals进行相应的设置,比如设置当前执行的操作码数组等,然后执行op_array。相关代码片段如下:
执行op_array的execute函数的函数定义在zend_vm_execute.h中。execute首先为op_array对应的函数在内核函数调用栈中分配一个新的栈帧。然后初始化相关的执行数据。
完成上述初始化工作后,将在一个while循环中执行当前op_array的每一个opcode。
opcode通过OPLINE->handler(execute_data TSRMLS_CC)
执行,一个opcode的执行结果有四种状态,根据四种状态分别执行不同的流程。这里我们主要看状态为2的情况,opcode的执行结构状态为2表示该opcode是一个函数调用操作码,这种情况下,首先替换当前的op_array,op_array = EG(active_op_array)
,然后跳转到zend_vm_enter处,为新函数分配堆栈并初始化执行环境。
我们在execute函数中看到了为函数调用分配堆栈相关的操作,但是为什么没有函数调用完毕后的释放堆栈相关的操作呢?
php用户定义函数,无论是否有返回值,其对应的op_array都会有RETURN操作码,而RETURN操作码就是负责释放堆栈的。具体的实现在zend_vm_execute.h文件的zend_leave_helper_SPEC
函数中,。