zend_execute: op执行过程详解

【走进php内核】之 Zend引擎执行过程 Zend引擎主要包含两个核心部分:编译、执行: 前面分析了Zend的编译过程以及PHP用户函数的实现,接下来分析下Zend引擎的执行过程。 1 数据结构 执行流程中有几个重要的数据结构,先看下这几个结构。 1.1 opcode opcode是将PHP代码编译产生的Zend虚拟机可识别的指令,php7共有173个opcode,定义在zend_vm_opcodes.h中,PHP中的所有语法实现都是由这些opcode组成的。 struct _zend_op { const void *h 阅读详情

    解释器引擎最终执行op的函数是zend_execute,实际上zend_execute是一个函数指针,在引擎初始化的时候zend_execute默认指向了execute,这个execute定义在{PHPSRC}/Zend/zend_vm_execute.h:

 

 

此函数的参数为op_array,这是一个指向zend_op_array的指针,op_array是在编译过程中生成,这里有必要介绍一下zend_op_array这个类型.

 

 

zend_op_array简介

此类型的定义在{PHPSRC}/Zend/zend_compile.h:

 

此结构比较复杂,我们目前只介绍最基本的几个字段。

1.type:

    op_array的类型,首先需要说明的是,一段PHP代码被编译之后,虽然返回的是一个zend_op_array指针,但是实际上生成的zend_op_array结构可能不止一个,通过这个结构中的一些字段,例如function_name ,num_args等你也许会发现这个zend_op_array结构似乎能和函数产生一定的联系,确实如此,用户自定义的函数,以及用户定义的类的方法,都是一个zend_op_array结构,这些zend_op_array结构在编译过程中被保存在某些地方,例如用户自定义的函数被保存进了GLOBAL_FUNCTION_TABLE,这个是全局函数符号表,通过函数名可以在此表中检索到函数体。那么编译后返回的那个zend_op_array指针是什么呢,其实编译后返回的zend_op_array是执行的一个入口,也可以认为它是最外层,即不在任何函数体内的全局代码组成的op_array。然而全局代码,用户自定义函数,用户自定义的方法都拥有相同的type值:2 ,type可取值的宏定义为:

 

可以看到全局代码,用户函数,用户方法都对应的是ZEND_USER_FUNCTION,这个也是最常见的type了,其中ZEND_EVAL_CODE对应的是eval函数中的PHP代码,所以我们可以想到,eval函数参数中的PHP代码也会被编译成单独的zend_op_array。

 

2.function_name

    如果op_array是由用户定义的函数或则方法编译而生成,那么此字段对应函数的名字,如果是全局代码或则是eval部分的代码,那么此字段为控制

 

3.opcodes

    这个字段类型为zend_op *,因此这是一个zend_op的数组,这个数组保存的就是此编译过程中生成的op,如果不了解zend_op,可以看看之前的文章 OPcode简介 , 这个字段是最重要的部分了,zend_execute最终就是执行这里保存的op。

 

现在基本对参数op_array有了一定的了解,那么我们就开始进入到execute中

 

执行过程详解

  execute函数开始的时候是一些基础变量的申明,其中zend_execute_data *execute_data;是执行期的数据结构,此变量在进行一定的初始化之后将会被传递给每个op的handler函数作为参数,op在执行过程中随时有可能改变execute_data中的内容。

  第14行zend_vm_enter 这个跳转标签是作为虚拟机执行的入口,当op中涉及到函数调用的时候,就有可能会跳转到这里来执行函数体。

  第16行到第19行为execute_data分配空间

  第21行到第32行主要是对execute_data进行一些初始化,以及保存现场工作,要保存现场是因为在进入函数调用的时候,需要保存当前一些运行期间的数据,在函数调用结束之后再进行还原,可以想象为操作系统中进程调度,当进程在调出的时候需要保存寄存器等上下文环境,而当进程被调入的时候再取出来继续执行。

  第41行到第51行主要是在当前动态符号表中加入$this变量,这个是在调用对象的方法时才有必要进行。

  第58行开始的while无限循环就是开始执行op_array中的opcodes了,在第66行中调用当前执行的op的handler:

EX(opline)->handler(execute_data TSRMLS_CC))

  然后如果handler的返回值小于0则循环继续,如果大于0则进入一个switch结构:

 

当返回值为1时:

    execute函数将返回,执行也就结束了

当返回值为2时:

    op_array被重新设置,并跳转到zend_vm_enter ,这个一般是函数调用或则执行eval函数中的代码,将在新的上下文执行相关函数的op_array

当返回值为3时: 

    循环体继续继续执行,当然再继续执行之前,EX(opline)已经往后移了一位(可能多位),也就是已经指向了后面一个新的opline,于是继续执行新的opline

当返回其他值时:

    结束循环,报错,结束应该用return,也就是返回1

 

在op的handler中返回特定的值都被定义成了宏,例如{PHPSRC}/Zend/zend_execute.c中定义的:

以及在{PHPSRC}/Zend/zend_vm_execute.c中定义的:

 

简单介绍功能

ZEND_VM_NEXT_OPCODE():

    移动到下一条op,返回0,不进入switch,循环继续(这个是最常用到的)

ZEND_VM_SET_OPCODE(new_op):

    当前opline设置成new_op

ZEND_VM_JMP(new_op) :

    当前opline设置成new_op,返回0,不进入switch,循环继续

ZEND_VM_INC_OPCODE():

    仅仅移动到下一条op

 

 

执行环境的切换

   在前面的内容已经提到,用户自定义函数,类方法,eval的代码都会编译成单独的op_array,那么当进行函数调用等操作时,必然涉及到调用前的op_array执行环境和新的函数的op_array执行环境的切换,这一段我们将以调用用户自定义函数来介绍整个切换过程如何进行。

  介绍此过程前必须了解执行环境的相关数据结构,涉及到执行环境的数据结构主要有两个:

1.执行期全局变量结构

   相关的定义在{PHPSRC}/Zend/zend_globals_macros.h:

 

 

这里是一个条件编译,ZTS表示线程安全启用,为了简化,我们这里以非线程安全模式的情况下来介绍,那么执行期的全局变量就是executor_globals,其类型为zend_executor_globals, zend_executor_globals的定义在{PHPSRC}/Zend/zend_globals.h,结构比较庞大,这里包含了整个执行期需要用到的各种变量,无论是哪个op_array在执行,都共用这一个全局变量,在执行过程中,此结构中的一些成员可能会改变,比如当前执行的op_array字段active_op_array,动态符号表字段active_symbol_table可能会根据不同的op_array而改变,This指针会根据在不同的对象环境而改变。

另外还定义了一个EG宏来取此变量中的字段值,此宏是针对线程安全和非线程安全模式的一个封装。

 

2.每个op_array自身的执行数据

   针对每一个op_array,都会有自己执行期的一些数据,在函数execute开始的时候我们能看到zend_vm_enter跳转标签下面就会初始一个局部变量execute_data,所以我们每次切换到新的op_array的时候,都会为新的op_array建立一个execute_data变量,此变量的类型为zend_execute_data的指针,相关定义在{PHPSRC}/Zend/zend_compile.h:

  

 

可以用EX宏来取其中的值:#define EX(element) execute_data->element

 

这里只简单介绍其中两个字段:

 opline:    当前正在执行的op

 prev_execute_data:     op_array环境切换的时候,这个字段用来保存切换前的op_array,此字段非常重要,他能将每个op_array的execute_data按照调用的先后顺序连接成一个单链表,每当一个op_array执行结束要还原到调用前op_array的时候,就通过当前的execute_data中的prev_execute_data字段来得到调用前的执行器数据

 

在executor_globals中的字段current_execute_data就是指向当前正在执行的op_array的execute_data。  

 

 

    再正式介绍之前还需要简单的介绍一下用户自定义函数的调用过程,详细的过程以后再函数章节中专门介绍,这里简单的说明一下:

在调用函数的时候,比如test()函数,会先在全局函数符号表中根据test来搜索相关的函数体,如果搜索不到则会报错函数没有定义,找到test的函数体之后,取得test函数的op_array,然后跳转到execute中的goto标签:zend_vm_enter,于是就进入到了test函数的执行环境。

 

下面我们将以一段简单的代码来介绍执行环境切换过程,例子代码:

 

这段代码非常简单,这样方便我们介绍原理,复杂的代码读者可以举一反三。

此代码编译之后会生成两个op_array,一个是全局代码的op_array,另外一个是test函数的op_array,其中全局代码中会通过函数调用进入到test函数的执行环境,执行结束之后,会返回到全局代码,然后代码结束。

 

 下面我们分几个阶段来介绍这段代码的过程,然后从中可以知道执行环境切换的方法。

1:进入execute函数,开始执行op_array ,这个op_array就是全局代码的op_array,我们暂时称其为op_array1

     首先在execute中为op_array1建立了一个execute_data数据,我们暂时命名为execute_data1,然后进行相关的初始化操作,其中比较重要的是:

EX(op_array) = op_array; // 设置op_array字段为当前执行的op_array,也就是全局代码的op_array1

EX(prev_execute_data) = EG(current_execute_data);//将全局执行数据中保存的当前op_array执行数据保存到op_array1的execute_data1的prev_execute_data字段,由于这是执行的第一个op_array,所以prev_execute_data实际上是空值,然后将执行期全局变量的current_execute_data设置成execute_data1,然后设置execute_data1的当前执行op,这样就可以开始执行当前的op了

 

2:在op_array1执行到test函数调用的的时候,首先从全局函数符号表中找到test的函数体,将函数体保存在execute_data1的function_state字段,然后从函数体中取到test的op_array,我们这里用op_array2来表示,并将op_array2赋值给EG(active_op_array):

EG(active_op_array) = &EX(function_state).function->op_array;

 

于是执行期全局变量的动态op_array字段指向了函数test的op_array,然后用调用ZEND_VM_ENTER();这个时候会先回到execute函数中的switch结构,并且满足以下case

    case 2:
     op_array = EG(active_op_array);
     goto zend_vm_enter;

 

EG(active_op_array)之前已经被我们设置为test函数的op_array2,于是在函数execute中,op_array变量就指向了test的op_array2,然后跳转到zend_vm_enter。

 

3:跳转到zend_vm_enter之后其实又回到了类似1中的步骤,此时为test的op_array2建立了它的执行数据execute_data,我们这里用execute_data2来表示。跟1中有些不同的是EX(prev_execute_data) = EG(current_execute_data);这个时候current_execute_data = execute_data1,也就是全局代码的执行执行期数据,然后EG(current_execute_data) = execute_data;这样current_execute_data就等于test的执行期数据execute_data2了,同时全局代码的execute_data1被保存在execute_data2的prev_execute_data字段。这个时候进行环境的切换已经完成,于是开始执行test函数。

 

4:test函数执行完之后就要返回到调用前的执行环境了,也就是全局代码执行环境,此阶段最重要的一个操作就是EG(current_execute_data) = EX(prev_execute_data);  在3中EX(prev_execute_data)已经设置成了全局代码的execute_data1,所以这样当前执行数据就变成了全局代码的执行数据,这样就成功的从函数test执行环境返回到了全局代码执行环境

 

这样,执行环境的切换过程就完成了,对于深层次的函数调用,原理一样,执行数据execute_data组成的单链表会更长。

手把手教你学 MATLAB 实战训练(6.5):基础实战实例 MATLAB 是一款强大的数学软件,广泛应用于工程计算、信号处理、控制系统设计、图像处理等领域。学习 MATLAB 不仅可以提高你的编程技能,还能帮助你解决实际的数学和工程问题。下面我将通过几个实战训练实例来教你如何使用 MATLAB。 阅读详情

相关推荐

机器人关节电机发热难题:从工程化角度拆解热管理解决方案(下)

摘要:本文系统分析了机器人关节电机与驱动系统的热管理方案,重点解决局部热点问题。针对不同功率场景提出了热管/均热板散热(适用于中小功率)和液冷散热(适用于高功率)的具体实施方案,详细比较了结构参数、材料选择、安装工艺和散热效果。特别强调了电机-驱动协同热管理的重要性,提出"工况联动+热路径共享"的核心理念,包括一体化散热结构设计和动态控制策略。最后介绍了热仿真与测试验证方法,确保方案的有效落地。通过系统级热管理,可显著降低温升(热点区域降温30-60℃),同时优化体积和能耗。

bother3000的博客 3329

php内核探索之zend_execute的具体执行过程

解释器引擎最终执行op的函数是zend_execute,实际上zend_execute是一个函数指针,在引擎初始化的时候zend_execute默认指向了execute,这个execute定义在{PHPSRC}/Zend/zend_vm_execute.h: ZEND_API void execute(zend_op_array *op_array T...

weixin_34405354的博客 407

Kaggle | 总奖池超200万美金的数学竞赛AIMO2冠军方案

••••结论1:通过结合高质量大规模数据集、工具集成推理和生成式选择方法,可以构建在挑战性数学问题上达到最先进水平的语言模型。本文的方法在 AIMO-2 竞赛中的胜利以及在多个数学基准上的 SOTA 结果验证了这一策略的有效性。结论2:发布 OpenMathReasoning 数据集、OpenMath-Nemotron 模型和代码是社区的宝贵财富。

hxshine的博客 1196

php %3eexecute,3.3.4 全局execute_data和opline

### 3.3.4 全局execute_data和oplineZend执行器在opcode的执行过程中,会频繁的用到execute_data和opline两个变量,execute_data为zend_execute_data结构,opline为当前执行的指令。普通的处理方式在执行每条opcode指令的handler时,会把execute_data地址作为参数传给handler使用,使用时先从当前栈...

weixin_33877981的博客 201

Fluent Meshing边界层设置保姆级教程:从y+理论到实战避坑,看完就会

本文详细解析了Fluent Meshing边界层设置的全过程,从y+理论基础到实战避坑技巧,帮助用户精准计算第一层高度并优化参数配置。通过案例演示和常见问题诊断,指导读者掌握边界层网格生成的核心技术,提升CFD模拟精度。特别针对不同湍流模型的y+要求提供具体参数建议。

weixin_30576827的博客 790

UVM自学笔记3——项目之三——从零开始搭建ahb_sramc验证平台

创建如下所示的目录结构: 2.在tb的文件下:顶层的testbench如下:在tb中,第24行的代码将uvm宏相关的内容include进来,第26行的代码把uvm_pkg包导入进来,然后将agent相关的包(ahb_pkg/sram_pkg),testcase相关的包(uvm_ahb_sram_test_pkg)以及env相关的包(uvm_ahb_sram_env_pkg)都导入进来,在第38行和39行将接口以virtual interface的形式配置给ahb_agent和sramc_agent下

ZhangShuHuai11的博客 9477

php op array,zend_op_array数据结构

zend_op_array数据结构zend_op_array数据结构#### 3.1.2.1 zend_op_array数据结构PHP主脚本会生成一个zend_op_array,每个function也会编译为独立的zend_op_array,所以从二进制程序的角度看zend_op_array包含着当前作用域下的所有堆栈信息,函数调用实际就是不同zend_op_array间的切换。![zend_co...

weixin_29000729的博客 334

ubuntu22.04下对使用linuxdeployqt发布的qt程序进行deb打包操作

【代码】ubuntu22.04下对使用linuxdeployqt发布的qt程序进行deb打包操作。

十三阿哥的博客 1403

[置顶] zend_execute: op执行过程详解

解释器引擎最终执行op的函数是zend_execute,实际上zend_execute是一个函数指针,在引擎初始化的时候zend_execute默认指向了execute,这个execute定义在{PHPSRC}/Zend/zend_vm_execute.h: ZEND_API void execute(zend_op_array *op_array TSRMLS_DC) ...

weixin_30752699的博客 124

从泛光成像到可寻址扫描:Flash激光雷达的车规级优势与技术演进

Flash激光雷达凭借固态架构优势成为自动驾驶感知技术的重要发展方向。该方案无机械部件,具备抗振动、长寿命等特点,同时CMOS工艺带来显著成本优势。但传统泛光式存在能量弥散问题,难以兼顾大视场和远距离探测。可寻址扫描技术通过分区点亮VCSEL阵列,有效提升局部能量密度,在保持固态可靠性的同时突破性能瓶颈。这种"电子扫描+分区点亮"的混合架构,使Flash激光雷达有望实现大视场、远距离、高可靠和低成本的多重突破,推动自动驾驶技术发展。

致力于lidar应用开发 235

2025华为OD机试真题+全流程解析+备考攻略+面试技巧+机考经验分享+Java/python/JS/C++/C语言实现

本专栏包含华为 OD 机试最新的 A卷+B卷+C卷+D卷+E卷题库,为全网最全、最新题库,500+真题。每一篇文章都包含了题目描述、解题思路以及实现代码。同时免费提供最新的在线OJ 刷题系统。已有上百名小伙伴通过刷真题题库通过了华为od机考,拒绝一切形式的代考,替考行为!!!以下是本专栏详细内容介绍:本专栏提供在线OJ刷题系统:OJ题库OJ系统注册&使用教程:OJ使用教程其他专栏以及答疑介绍:50+ 高频面试手撕真题,已有上百位小伙伴高频认证。1:机试没过,半年的冷冻期。2:已经订阅过我专栏的同学,推荐新人

MISAYAONE的博客 2225

【PHP7内核剖析】3.3 Zend引擎执行过程

更多《PHP7内核剖析》系列文章:https://github.com/pangudashu/php7-internal3.3 Zend引擎执行过程Zend引擎主要包含两个核心部分:编译、执行:前面分析了Zend的编译过程以及PHP用户函数的实现,接下来分析下Zend引擎的执行过程。3.3.1 数据结构执行流程中有几个重要的数据结构,先看下这几个结构。

盘古大叔 9345

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

八十八、PHP内核探索:zend_execute的具体执行过程 ☞ 解释器最终执行op的函数是zend_execute

解释器引擎最终执行op的函数是zend_execute,实际上zend_execute是一个函数指针,在引擎初始化的时候zend_execute默认指向了execute,这个execute定义在{PHPSRC}/Zend/zend_vm_execute.h: ZEND_API void execute(zend_op_array *op_array TSRMLS_DC) { z...

进阶之旅 494

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

88-zend_execute的具体执行过程

88-zend_execute的具体执行过程解释器引擎最终执行op的函数是zend_execute,实际上zend_execute是一个函数指针,在引擎初始化的时候zend_execute默认指向了execute,这个execute定义在{PHPSRC}/Zend/zend_vm_execute.h:ZEND_API void execute(zend_op_array *op_array TSRM

phantom66的博客 7831

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

php的op_array与execute_data的关系

今天稍微对php做下总结,首先介绍最重要的两个数据结构,以及两个结构间的数据传递 struct _zend_op_array { /* Common elements */ zend_uchar type; const char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_funct

xiaolei1982致力于Web开发 2957

zend_execute中op的执行

解释器引擎最终执行op的函数是zend_execute,实际上zend_execute是一个函数指针,在引擎初始化的时候zend_execute默认指向了execute,这个execute定义在{PHPSRC}/Zend/zend_vm_execute.h:   ZEND_API void execute(zend_op_array *op_array TSRMLS_D

辛星,前进的路上. 795

六十三、PHP内核探索:函数调用与执行 ☞ 调用zend_execute来执行zend_op_array

前面对函数的内部表示以及参数的传递,返回值都有了介绍,那函数是怎么被调用的呢?内置函数和用户定义函数在调用时会有什么不一样呢? 下面将介绍函数调用和执行的过程。 函数的调用 函数被调用需要一些基本的信息,比如函数的名称,参数以及函数的定义(也就是最终函数是怎么执行的), 从我们开发者的角度来看, 定义了一个函数我们在执行的时候自然知道这个函数叫什么名字,以及调用的时候给传递了什么参数,以及函数...

进阶之旅 1654

php zend opcheck,Zend 引擎执行器的核心代码

ZEND_API void execute(zend_op_array *op_array TSRMLS_DC){// 初始化执行上下文zend_execute_data execute_data;// 如果有异常就退出执行if (EG(exception)) {return;}/* Initialize execute_data */EX(fbc) = NULL; // 初始化正在调用的函数EX...

weixin_36399948的博客 242

PHP Zend Engine 内核解读:Zend 内存管理器与 ZEND_VM 操作码执行架构(基于 Zend/README.md)

本文基于 php-src 仓库中的 [Zend/README.md](https://link.gitcode.com/i/1d2d08413a22f0ed96618dc49c7a400f) 展开,完整继承其中两大核心主题:Zend 内存管理器(memory manager)的设计目标与调试手段,以及 ZEND_VM 操作码执行器的专用化(specialization)、线程化模型(threadi

gitblog_00362的博客 832

php op array,php的op_array与execute_data的关系

今天稍微对php做下总结,首先介绍最重要的两个数据结构,以及两个结构间的数据传递struct _zend_op_array {/* Common elements */zend_uchar type;const char *function_name;zend_class_entry *scope;zend_uint fn_flags;union _zend_function *prototype...

weixin_32973973的博客 261

PHP7引擎,PHP7内核剖析7之Zend引擎执行过程

本篇文章的内容介绍的是关于PHP7内核剖析7之Zend引擎执行过程 ,现在分享给大家,有需要的朋友可以参考一下1.EG(executor_globals/zend_executor_globals)PHP整个生命周期中最主要的一个结构,是一个全局变量,在main执行前分配(非ZTS下),直到PHP退出,它记录着当前请求全部的信息2.EX(execute_data/zend_execute_data...

weixin_35122299的博客 273

php的zend引擎执行过程 一

1. Zend引擎主要包含两个核心部分:编译、执行: 执行阶段主要用到的数据结构: opcode: php代码编译产生的zend虚拟机可识别的指令,php7有173个opcode,定义在zend_vm_opcodes.hPHP中的所有语法实现都是由这些opcode组成的。 ...

weixin_30622107的博客 294

php7zend引擎架构,(PHP7内核剖析-7) Zend引擎执行过程

1.EG(executor_globals/zend_executor_globals)PHP整个生命周期中最主要的一个结构,是一个全局变量,在main执行前分配(非ZTS下),直到PHP退出,它记录着当前请求全部的信息 2.EX(execute_data/zend_execute_data)在执行过程中最核心的一个结构,每次函数的调用、include/require、eval等都会生成一个新的结...

weixin_34966346的博客 291

zend_vm_execute.h,知识体系一共包含哪些部分?底层原理是什么?

是PHP代码的“最终执行者”,其核心功能是定义opcode的处理函数、实现指令执行循环、维护执行上下文。它通过“分发表调度”“类型专项优化”“组件协作”等机制,让PHP的动态特性得以高效实现。作为PHP开发者,不需要阅读这个文件的源码,但理解其背后的执行逻辑,能帮你写出更高效、更稳定的代码——就像了解发动机原理的司机,知道如何驾驶能更省油、更少出故障。当你写出时,能想到它背后是ZEND_ADD处理函数的调用、zval变量的操作、执行指针的移动,这种认知会让你对PHP的理解从“语法层面”深入到“执行层面”

长风破浪会有时的博客 1210

十七、PHP内核探索:解释器的执行过程 ☞ 引擎是如何执行PHP代码的

这里将介绍引擎内部执行一个PHP脚本的流程,以CLI SAPI为例子来对流程中核心的部分做简单介绍,省去一些初始化及清理操作。 CLI(Command Line Interface)即PHP的命令行模式,现在此SAPI是默认安装的,我们在服务器上安装完PHP之后,一般会生成一个可执行文件,假设此文件为/usr/local/bin/php,那么我们在SHELL下可以用以下命令来执行一个PHP脚本...

进阶之旅 297
上一篇: OPcode简介
下一篇: 变量的内部存储:引用和计数
phpkernel
博客等级 码龄16年 96粉丝 13原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值