0

0

PHP7源码:PHP虚拟机的详细解析

不言

不言

发布时间:2018-08-08 14:04:58

|

4814人浏览过

|

来源于php中文网

原创

本篇文章给大家带来的内容是关于php7源码:php虚拟机的详细解析,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

1.从物理机说起

虚拟机也是计算机,设计思想和物理机有很多相似之处;

1.1冯诺依曼体系结构

冯·诺依曼是当之无愧的数字计算机之父,当前计算机都采用的是冯诺依曼体系结构;设计思想主要包含以下几个方面:

立即学习PHP免费学习笔记(深入)”;

  • 指令和数据不加区别混合存储在同一个存储器中,它们都是内存中的数据。现代CPU的保护模式,每个内存段都有段描述符,这个描述符记录着这个内存段的访问权限(可读,可写,可执行)。这就变相的指定了哪些内存中存储的是指令哪些是数据);

  • 存储器是按地址访问的线性编址的一维结构,每个单元的位数是固定的;

  • 数据以二进制表示;

  • 指令由操作码和操作数组成。操作码指明本指令的操作类型,操作数指明操作数本身或者操作数的地址。操作数本身并无数据类型,它的数据类型由操作码确定;任何架构的计算机都会对外提供指令集合;

  • 运算器通过执行指令直接发出控制信号控制计算机各项操作。由指令计数器指明待执行指令所在的内存地址。指令计数器只有一个,一般按顺序递增,但执行顺序可能因为运算结果或当时的外界条件而改变;

2631839729-5b6a6ac8d5766_articlex.png

1.2汇编语言简介

任何架构的计算机都会提供一组指令集合;

指令由操作码和操作数组成;操作码即操作类型,操作数可以是一个立即数或者一个存储地址;每条指令可以有0、1或2个操作数;

指令就是一串二进制;汇编语言是二进制指令的文本形式;

push   %ebx
mov    %eax, [%esp+8]
mov    %ebx, [%esp+12]
add    %eax, %ebx
pop    %ebx

push、mov、add、pop等就是操作码;
%ebx寄存器;[%esp+12]内存地址;
操作数只是一块可存取数据的存储区;操作数本身并无数据类型,它的数据类型由操作码确定;
如movb传送字节,movw传送字,movl传送双字等

1.3 函数调用栈

过程(函数)是对代码的封装,对外暴露的只是一组指定的参数和一个可选的返回值;可以在程序中不同的地方调用这个函数;假设过程P调用过程Q,Q执行后返回过程P;为了实现这一功能,需要考虑三点:

  • 指令跳转:进入过程Q的时候,程序计数器必须被设置为Q的代码的起始地址;在返回时,程序计数器需要设置为P中调用Q后面那条指令的地址;

  • 数据传递:P能够向Q提供一个或多个参数,Q能够向P返回一个值;

  • 内存分配与释放:Q开始执行时,可能需要为局部变量分配内存空间,而在返回前,又需要释放这些内存空间;

大多数的语言过程调用都采用了栈数据结构提供的内存管理机制;如下图所示:

6391936-5b6a6e2478332_articlex.png

函数的调用与返回即对应的是一系列的入栈与出栈操作;
函数在执行时,会有自己私有的栈帧,局部变量就是分配在函数私有栈帧上的;
平时遇到的栈溢出就是因为调用函数层级过深,不断入栈导致的;

2.PHP虚拟机

虚拟机也是计算机,参考物理机的设计,设计虚拟机时,首先应该考虑三个要素:指令,数据存储,函数栈帧;

下面从这三点详细分析PHP虚拟机的设计思路;

2.1指

2.1.1 指令类型

任何架构的计算机都需要对外提供一组指令集,其代表计算机支持的一组操作类型;

PHP虚拟机对外提供186种指令,定义在zend_vm_opcodes.h文件中;

//加、减、乘、除等
#define ZEND_ADD                               1
#define ZEND_SUB                               2
#define ZEND_MUL                               3
#define ZEND_p                               4
#define ZEND_MOD                               5
#define ZEND_SL                                6
#define ZEND_SR                                7
#define ZEND_CONCAT                            8
#define ZEND_BW_OR                             9
#define ZEND_BW_AND                           10
……………………

2.1.2 指令

2.1.2.1指令的表示

指令由操作码和操作数组成;操作码指明本指令的操作类型,操作数指明操作数本身或者操作数的地址;

PHP虚拟机定义指令格式为:操作码 操作数1 操作数2 返回值;其使用结构体_zend_op表示一条指令:

struct _zend_op {
    const void *handler;    //指针,指向当前指令的执行函数
    znode_op op1;           //操作数1         
    znode_op op2;           //操作数2
    znode_op result;        //返回值
    uint32_t extended_value;//扩展
    uint32_t lineno;        //行号
    zend_uchar opcode;      //指令类型
    zend_uchar op1_type;    //操作数1的类型(此类型并不代表字符串、数组等数据类型;其表示此操作数是常量,临时变量,编译变量等)
    zend_uchar op2_type;    //操作数2的类型
    zend_uchar result_type; //返回值的类型
};

2.1.2.2 操作数的表示

从上面可以看到,操作数使用结构体znode_op表示,定义如下:

php配置文件php.ini的中文注释版
php配置文件php.ini的中文注释版

php配置文件php.ini的中文注释版是一本由多位作者编著的有关PHP内部实现的开源书籍。从环境准备到代码实现,从实现过程到细节延展,从变量、函数、对象到内存、Zend虚拟机…… 如此种种,道尽PHP之风流。

下载

constant、var、num等都是uint32_t类型的,这怎么表示一个操作数呢?(既不是指针不能代表地址,也无法表示所有数据类型);
其实,操作数大多情况采用的相对地址表示方式,constant等表示的是相对于执行栈帧首地址的偏移量;
另外,_znode_op结构体中有个zval *zv字段,其也可以表示一个操作数,这个字段是一个指针,指向的是zval结构体,PHP虚拟机支持的所有数据类型都使用zval结构体表示;

typedef union _znode_op {
        uint32_t      constant;
        uint32_t      var;
        uint32_t      num;
        uint32_t      opline_num;
    #if ZEND_USE_ABS_JMP_ADDR
        zend_op       *jmp_addr;
    #else
        uint32_t      jmp_offset;
    #endif
    #if ZEND_USE_ABS_CONST_ADDR
        zval          *zv;
    #endif
} znode_op;

2.2 数据存储

PHP虚拟机支持多种数据类型:整型、浮点型、字符串、数组,对象等;PHP虚拟机如何存储和表示多种数据类型?

2.1.2.2节指出结构体_znode_op代表一个操作数;操作数可以是一个偏移量(计算得到一个地址,即zval结构体的首地址),或者一个zval指针;PHP虚拟机使用zval结构体表示和存储多种数据;

struct _zval_struct {
    zend_value        value;            //存储实际的value值
    union {
        struct {                        //一些标志位
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         //重要;表示变量类型
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
    union {                                 //其他有用信息
        uint32_t     next;                 /* hash collision chain */
        uint32_t     cache_slot;           /* literal cache slot */
        uint32_t     lineno;               /* line number (for ast nodes) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach position */
        uint32_t     fe_iter_idx;          /* foreach iterator index */
        uint32_t     access_flags;         /* class constant access flags */
        uint32_t     property_guard;       /* single property guard */
    } u2;
};

zval.u1.type表示数据类型, zend_types.h文件定义了以下类型:

#define IS_UNDEF                    0
#define IS_NULL                     1
#define IS_FALSE                    2
#define IS_TRUE                     3
#define IS_LONG                     4
#define IS_DOUBLE                   5
#define IS_STRING                   6
#define IS_ARRAY                    7
#define IS_OBJECT                   8
#define IS_RESOURCE                 9
#define IS_REFERENCE                10
…………

zend_value存储具体的数据内容,结构体定义如下:

_zend_value占16字节内存;long、double类型会直接存储在结构体;引用、字符串、数组等类型使用指针存储;

代码中根据zval.u1.type字段,判断数据类型,以此决定操作_zend_value结构体哪个字段;

可以看出,字符串使用zend_string表示,数组使用zend_array表示…

typedef union _zend_value {
    zend_long         lval;            
    double            dval;            
    zend_refcounted  *counted;
    zend_string      *str;
    zend_array       *arr;
    zend_object      *obj;
    zend_resource    *res;
    zend_reference   *ref;
    zend_ast_ref     *ast;
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

如下图为PHP7中字符串结构图:

2081089099-5b6a6ef88f01a_articlex.png

2.3 再谈指令

2.1.2.1指出,指令使用结构体_zend_op表示;其中最主要2个属性:操作函数,操作数(两个操作数和一个返回值);

操作数的类型(常量、临时变量等)不同,同一个指令对应的handler函数也会不同;操作数类型定义在 Zend/zend_compile.h文件:

//常量
#define IS_CONST    (1<<0)
 
//临时变量,用于操作的中间结果;不能被其他指令对应的handler重复使用
#define IS_TMP_VAR  (1<<1)
 
//这个变量并不是PHP代码中声明的变量,常见的是返回的临时变量,比如$a=time(), 函数time返回值的类型就是IS_VAR,这种类型的变量是可以被其他指令对应的handler重复使用的
#define IS_VAR      (1<<2)
#define IS_UNUSED   (1<<3)  /* Unused variable */
 
//编译变量;即PHP中声明的变量;
#define IS_CV       (1<<4)  /* Compiled variable */

操作函数命名规则为:ZEND_[opcode]_SPEC_(操作数1类型)_(操作数2类型)_(返回值类型)_HANDLER

比如赋值语句就有以下多种操作函数:

ZEND_ASSIGN_SPEC_VAR_CONST_RETVAL_UNUSED_HANDLER,
ZEND_ASSIGN_SPEC_VAR_TMP_RETVAL_UNUSED_HANDLER,
ZEND_ASSIGN_SPEC_VAR_VAR_RETVAL_UNUSED_HANDLER,
ZEND_ASSIGN_SPEC_VAR_CV_RETVAL_UNUSED_HANDLER,
…

对于$a=1,其操作函数为: ZEND_ASSIGN_SPEC_CV_CONST_RETVAL_UNUSED_HANDLER;函数实现为:

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_SPEC_CV_CONST_RETVAL_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE
 
    zval *value;
    zval *variable_ptr;
 
    SAVE_OPLINE();
    //获取op2对应的值,也就是1
    value = EX_CONSTANT(opline->op2);
    //在execute_data中获取op1的位置,也就是$a(execute_data类似函数栈帧,后面详细分析)
    variable_ptr = _get_zval_ptr_cv_undef_BP_VAR_W(execute_data, opline->op1.var);
     
    //赋值
    value = zend_assign_to_variable(variable_ptr, value, IS_CONST);
    if (UNEXPECTED(0)) {
        ZVAL_COPY(EX_VAR(opline->result.var), value);
    }
 
    ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

2.4 函数栈帧

2.4.1指令集

上面分析了指令的结构与表示,PHP虚拟机使用_zend_op_array表示指令的集合:

struct _zend_op_array {
    …………
    //last表示指令总数;opcodes为存储指令的数组;
    uint32_t last;
    zend_op *opcodes;
    //变量类型为IS_CV的个数
    int last_var;
    //变量类型为IS_VAR和IS_TEMP_VAR的个数
    uint32_t T;
    //存放IS_CV类型变量的数组
    zend_string **vars;
 
    …………
     
    //静态变量
    HashTable *static_variables;
 
    //常量个数;常量数组
    int last_literal;
    zval *literals;
 
    …
};

注意: last_var代表IS_CV类型变量的个数,这种类型变量存放在vars数组中;在整个编译过程中,每次遇到一个IS_CV类型的变量(类似于$something),就会去遍历vars数组,检查是否已经存在,如果不存在,则插入到vars中,并将last_var的值设置为该变量的操作数;如果存在,则使用之前分配的操作数

2.4.2 函数栈帧

PHP虚拟机实现了与1.3节物理机类似的函数栈帧结构;

使用 _zend_vm_stack表示栈结构;多个栈之间使用prev字段形成单向链表;top和end指向栈低和栈顶,分别为zval类型的指针;

struct _zend_vm_stack {
    zval *top;
    zval *end;
    zend_vm_stack prev;
};

考虑如何设计函数执行时候的帧结构:当前函数执行时,需要存储函数编译后的指令,需要存储函数内部的局部变量等(2.1.2.2节指出,操作数使用结构体znode_op表示,其内部使用uint32_t表示操作数,此时表示的就是当前zval变量相对于当前函数栈帧首地址的偏移量);

PHP虚拟机使用结构体_zend_execute_data存储当前函数执行所需数据;

struct _zend_execute_data {
    //当前指令指令
    const zend_op       *opline; 
    //当前函数执行栈帧
    zend_execute_data   *call; 
    //函数返回数据          
    zval                *return_value;
    zend_function       *func;            
    zval                 This;      /* this + call_info + num_args */
    //调用当前函数的栈帧       
    zend_execute_data   *prev_execute_data;
    //符号表
    zend_array          *symbol_table;
#if ZEND_EX_USE_RUN_TIME_CACHE
    void               **run_time_cache;  
#endif
#if ZEND_EX_USE_LITERALS
    //常量数组
    zval                *literals;        
#endif
};

函数开始执行时,需要为函数分配相应的函数栈帧并入栈,代码如下:

static zend_always_inline zend_execute_data *zend_vm_stack_push_call_frame(uint32_t call_info, zend_function *func, uint32_t num_args, zend_class_entry *called_scope, zend_object *object)
{
    //计算当前函数栈帧需要内存空间大小
    uint32_t used_stack = zend_vm_calc_used_stack(num_args, func);
 
    //根据栈帧大小分配空间,入栈
    return zend_vm_stack_push_call_frame_ex(used_stack, call_info,
        func, num_args, called_scope, object);
}
 
//计算函数栈帧大小
static zend_always_inline uint32_t zend_vm_calc_used_stack(uint32_t num_args, zend_function *func)
{
    //_zend_execute_data大小(80字节/16字节=5)+参数数目
    uint32_t used_stack = ZEND_CALL_FRAME_SLOT + num_args;
 
    if (EXPECTED(ZEND_USER_CODE(func->type))) {
        //当前函数临时变量等数目
        used_stack += func->op_array.last_var + func->op_array.T - MIN(func->op_array.num_args, num_args);
    }
 
    //乘以16字节
    return used_stack * sizeof(zval);
}
 
//入栈
static zend_always_inline zend_execute_data *zend_vm_stack_push_call_frame_ex(uint32_t used_stack, uint32_t call_info, zend_function *func, uint32_t num_args, zend_class_entry *called_scope, zend_object *object)
{
    //上一个函数栈帧地址
    zend_execute_data *call = (zend_execute_data*)EG(vm_stack_top);
 
    //移动函数调用栈top指针
    EG(vm_stack_top) = (zval*)((char*)call + used_stack);
    //初始化当前函数栈帧
    zend_vm_init_call_frame(call, call_info, func, num_args, called_scope, object);
    //返回当前函数栈帧首地址
    return call;
}

从上面分析可以得到函数栈帧结构图如下所示:

1497251327-5b6a6fb3222d7_articlex.png

总结

PHP虚拟机也是计算机,有三点是我们需要重点关注的:指令集(包含指令处理函数)、数据存储(zval)、函数栈帧;

此时虚拟机已可以接受指令并执行指令代码;

但是,PHP虚拟机是专用执行PHP代码的,PHP代码如何能转换为PHP虚拟机可以识别的指令呢——编译;

PHP虚拟机同时提供了编译器,可以将PHP代码转换为其可以识别的指令集合;

理论上你可以自定义任何语言,只要实现编译器,能够将你自己的语言转换为PHP可以识别的指令代码,就能被PHP虚拟机执行;

相关文章推荐:

PHP7.0和php7.1中的语法新特性的总结

PHP中如何将session存入数据库并使用(附代码)

PHP中时间函数strtotime() 函数的原理讲解

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

php

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
云朵浏览器入口合集
云朵浏览器入口合集

本专题整合了云朵浏览器入口合集,阅读专题下面的文章了解更多详细地址。

0

2026.01.20

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

20

2026.01.20

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

62

2026.01.19

java用途介绍
java用途介绍

本专题整合了java用途功能相关介绍,阅读专题下面的文章了解更多详细内容。

87

2026.01.19

java输出数组相关教程
java输出数组相关教程

本专题整合了java输出数组相关教程,阅读专题下面的文章了解更多详细内容。

39

2026.01.19

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

10

2026.01.19

xml格式相关教程
xml格式相关教程

本专题整合了xml格式相关教程汇总,阅读专题下面的文章了解更多详细内容。

13

2026.01.19

PHP WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

19

2026.01.19

微信聊天记录删除恢复导出教程汇总
微信聊天记录删除恢复导出教程汇总

本专题整合了微信聊天记录相关教程大全,阅读专题下面的文章了解更多详细内容。

160

2026.01.18

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP课程
PHP课程

共137课时 | 8.9万人学习

JavaScript ES5基础线上课程教学
JavaScript ES5基础线上课程教学

共6课时 | 8.5万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号