Maison > développement back-end > tutoriel php > Code source PHP7 : analyse détaillée de la machine virtuelle PHP

Code source PHP7 : analyse détaillée de la machine virtuelle PHP

不言
Libérer: 2023-04-03 17:26:01
original
4520 Les gens l'ont consulté

Le contenu de cet article concerne le code source PHP7 : une analyse détaillée de la machine virtuelle PHP Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

1. Commençons par les machines physiques

Les machines virtuelles sont également des ordinateurs, et leurs idées de conception présentent de nombreuses similitudes avec les machines physiques

1.1 Architecture de Von Neumann

Von Neumann est le père incontesté des ordinateurs numériques. Les ordinateurs actuels utilisent l'architecture de von Neumann ; les idées de conception incluent principalement les aspects suivants :

    Les instructions et les données sont mélangées et stockées dans la même mémoire sans distinction. Ce sont toutes des données en mémoire. Dans le mode protégé des CPU modernes, chaque segment mémoire possède un descripteur de segment. Ce descripteur enregistre les droits d'accès (en lecture, en écriture et exécutable) de ce segment mémoire. Ceci spécifie déguisé quelles mémoires stockent des instructions et lesquelles sont des données);
  • La mémoire est une structure unidimensionnelle adressée linéairement à laquelle on accède par adresse. Le nombre de bits dans chaque unité est fixe. ;
  • les données sont représentées en binaire ; les instructions
  • sont composées d'opcodes et d'opérandes. L'opcode spécifie le type d'opération de cette instruction et l'opérande spécifie l'opérande lui-même ou l'adresse de l'opérande. L'opérande lui-même n'a pas de type de données, et son type de données est déterminé par l'opcode ; toute architecture informatique fournira un ensemble d'instructions à l'extérieur
  • L'opérateur envoie directement des signaux de contrôle ; contrôler l'ordinateur en exécutant des instructions diverses opérations. Le compteur d'instructions indique l'adresse mémoire où se trouve l'instruction à exécuter. Il n'y a qu'un seul compteur d'instructions, qui augmente généralement de manière séquentielle, mais l'ordre d'exécution peut changer en raison des résultats de l'opération ou des conditions externes du moment

Code source PHP7 : analyse détaillée de la machine virtuelle PHP1.2 Introduction au langage assembleur

Un ordinateur de n'importe quelle architecture fournira un ensemble d'instructions

Les instructions sont constituées d'opcodes et d'opérandes ; sont des types d'opérations. L'opérande peut être un nombre immédiat ou une adresse de stockage ; chaque instruction peut avoir 0, 1 ou 2 opérandes ; l'instruction

est une chaîne de langage assembleur binaire ;

push, mov, add, pop, etc. sont des codes d'opération ;

%ebx Register ; [%esp+12] adresse mémoire
push   %ebx
mov    %eax, [%esp+8]
mov    %ebx, [%esp+12]
add    %eax, %ebx
pop    %ebx
Copier après la connexion
L'opérande est juste une zone de stockage ; peut accéder aux données ; opération Le nombre lui-même n'a pas de type de données et son type de données est déterminé par le code d'opération

Par exemple, movb transfère des octets, movw transfère des mots, movl transfère des mots doubles, etc.


1.3 Pile d'appels de fonction

Un processus (fonction) est une encapsulation du code. Ce qui est exposé au monde extérieur n'est qu'un ensemble de paramètres spécifiés et une valeur de retour facultative ; la fonction peut être appelée à différents endroits du programme ; en supposant que le processus P appelle le processus Q , retournez au processus P après l'exécution de Q afin d'implémenter cette fonction, trois points doivent être pris en compte :

Instruction jump: Lors de l'entrée dans le processus Q, le compteur du programme doit être réglé sur le Code de Q L'adresse de départ de, Q peut renvoyer une valeur à P

  • Allocation de mémoire; et release : lorsque Q démarre l'exécution, il peut avoir besoin d'allouer de l'espace mémoire pour les variables locales, et avant de revenir, ces mémoires doivent être libérées. Espace

  • La plupart des appels de procédure de langage utilisent la mémoire ; mécanisme de gestion fourni par la structure de données de la pile ; comme le montre la figure ci-dessous :

L'appel et le retour d'une fonction correspondent à une série de push et opérations pop ;

Lorsqu'une fonction est exécutée, elle aura son propre cadre de pile privé, et les variables locales sont allouées dans le cadre privé de la fonction Sur le cadre de pile ;

Le débordement de pile généralement rencontré est causé par la fonction appelante. le niveau est trop profond et pousse constamment dans la pile ;

Code source PHP7 : analyse détaillée de la machine virtuelle PHP2. Machine virtuelle PHP

Les machines virtuelles sont également des ordinateurs faisant référence à la conception de machines physiques, lors de la conception. une machine virtuelle, vous devez d'abord considérer trois éléments : les instructions, le stockage des données et les cadres de pile de fonctions

Ce qui suit est une analyse détaillée de la machine virtuelle PHP à partir de ces trois points. Idée de conception ; >

2.1 fait référence à

2.1.1 Type d'instruction

Les ordinateurs de toute architecture doivent fournir un ensemble d'instructions externes, qui représente un ensemble de types d'opérations pris en charge par l'ordinateur ;

La machine virtuelle PHP fournit 186 instructions externes, qui sont définies dans le fichier zend_vm_opcodes.h

2.1 ; .2 Instructions

2.1.2.1 Représentation des instructions
//加、减、乘、除等
#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
……………………
Copier après la connexion

Les instructions sont constituées d'opcodes et d'opérandes ; type d'opération de cette instruction, et l'opérande spécifie l'opérande lui-même ou l'adresse de l'opérande ; La machine virtuelle PHP définit le format de l'instruction comme : opcode opérande 1 opérande 2 valeur de retour elle utilise une structure que _zend_op représente ; une instruction :

2.1.2.2 Représentation des opérandes

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

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;
Copier après la connexion

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;
};
Copier après la connexion

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
…………
Copier après la connexion

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;
Copier après la connexion

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

Code source PHP7 : analyse détaillée de la machine virtuelle PHP

2.3 再谈指令

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

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

//常量
#define IS_CONST    (1<p>操作函数命名规则为:ZEND_[opcode]_SPEC_(操作数1类型)_(操作数2类型)_(返回值类型)_HANDLER</p><p>比如赋值语句就有以下多种操作函数:</p><pre class="brush:php;toolbar:false">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,
…
Copier après la connexion

对于$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();
}
Copier après la connexion

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;
 
    …
};
Copier après la connexion

注意: 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;
};
Copier après la connexion

考虑如何设计函数执行时候的帧结构:当前函数执行时,需要存储函数编译后的指令,需要存储函数内部的局部变量等(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
};
Copier après la connexion

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

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;
}
Copier après la connexion

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

Code source PHP7 : analyse détaillée de la machine virtuelle PHP

总结

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

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

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

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

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

Articles connexes recommandés :

Un résumé des nouvelles fonctionnalités de syntaxe de PHP7.0 et php7.1

Comment stocker des sessions dans la base de données en PHP Et utiliser (avec code)

Explication du principe de la fonction time strtotime() en PHP

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
php
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal