Table des matières
OPCODE
方法调用
逻辑跳转
结语
Maison développement back-end tutoriel php Analyse du noyau PHP-Explication détaillée de la machine virtuelle Zend

Analyse du noyau PHP-Explication détaillée de la machine virtuelle Zend

Mar 18, 2017 am 09:58 AM

PHP est un langage interprété. Pour les langages interprétés tels que Java, Python, Ruby et Javascript, le code que nous écrivons ne sera pas compilé en code machine à exécuter, mais sera compilé en code intermédiaire et exécuter sur la machine virtuelle (VM). La machine virtuelle qui exécute PHP s'appelle la machine virtuelle Zend. Aujourd'hui, nous allons approfondir le noyau et explorer les principes de fonctionnement de la machine virtuelle Zend.

OPCODE

Qu'est-ce qu'OPCODE ? C'est une instruction que la machine virtuelle peut reconnaître et traiter. La machine virtuelle Zend contient une série d'OPCODE. La machine virtuelle OPCODE peut faire beaucoup de choses. Voici quelques exemples d'OPCODE :

  • ZEND_ADD Ajouter deux opérandes.

  • ZEND_NEW Créez un objet PHP.

  • ZEND_ECHO Sort le contenu sur la sortie standard.

  • ZEND_EXIT Quittez PHP.

PHP a défini 186 opérations (avec la mise à jour de PHP, plus de types d'OPCODE seront certainement pris en charge. La définition et l'implémentation de tous les OPCODE peuvent être trouvées dans le code source fichier (le contenu de ce fichier n'est pas du code C natif, mais un modèle, la raison sera expliquée plus tard). zend/zend_vm_def.h

Jetons un coup d'œil à la façon dont PHP conçoit la structure de données OPCODE :

struct _zend_op {
	const void *handler;
	znode_op op1;
	znode_op op2;
	znode_op result;
	uint32_t extended_value;
	uint32_t lineno;
	zend_uchar opcode;
	zend_uchar op1_type;
	zend_uchar op2_type;
	zend_uchar result_type;
};
Copier après la connexion
Regardez attentivement la structure de données d'OPCODE et voyez si vous pouvez trouver la sensation du langage assembleur. Chaque OPCODE contient deux opérandes,

et op1 Le pointeur op2 pointe vers la fonction handler qui effectue l'opération OPCODE. Le résultat du traitement de la fonction sera enregistré dans Medium. result

Prenons un exemple simple :

<?php
$b = 1;
$a = $b + 2;
Copier après la connexion
On voit à travers l'extension vld qu'après compilation, le code ci-dessus génère l'OPCODE de l'instruction ZEND_ADD.

compiled vars:  !0 = $b, !1 = $a
line     #* E I O op                           
fetch          
ext  
return  
operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 1
   3     1        ADD                                              ~3      !0, 2
         2        ASSIGN                                                   !1, ~3
   8     3      > RETURN                                                   1
Copier après la connexion
Parmi eux, la deuxième ligne est l'OPCODE de la commande

. On voit qu'il reçoit 2 opérandes, ZEND_ADD est la variable op1, $b est la constante numérique 1, et le résultat renvoyé est stocké dans une variable temporaire. Dans le fichier op2, on peut trouver l'implémentation de la fonction correspondant à l'instruction ZEND_ADD : zend/zend_vm_def.h

ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMPVAR|CV, CONST|TMPVAR|CV)
{
	USE_OPLINE
	zend_free_op free_op1, free_op2;
	zval *op1, *op2, *result;

	op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
	op2 = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
	if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_LONG)) {
		if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_LONG)) {
			result = EX_VAR(opline->result.var);
			fast_long_add_function(result, op1, op2);
			ZEND_VM_NEXT_OPCODE();
		} else if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_DOUBLE)) {
			result = EX_VAR(opline->result.var);
			ZVAL_DOUBLE(result, ((double)Z_LVAL_P(op1)) + Z_DVAL_P(op2));
			ZEND_VM_NEXT_OPCODE();
		}
	} else if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_DOUBLE)) {

	...
}
Copier après la connexion
Le code ci-dessus n'est pas du code C natif, mais un modèle.

Pourquoi fais-tu ça ? Parce que PHP est un langage faiblement typé et que le C, qui y est implémenté, est un langage fortement typé. Les langages faiblement typés prennent en charge la correspondance de type automatique et la mise en œuvre de la correspondance de type automatique, tout comme le code ci-dessus, gère les paramètres de différents types par jugement. Imaginez, si chaque OPCODE doit déterminer le type de paramètres entrants lors du traitement, alors les performances deviendront inévitablement un énorme problème (une requête peut devoir traiter des dizaines de milliers d'OPCODE).

Y a-t-il un moyen ? Nous avons constaté qu'au moment de la compilation, nous pouvons déjà déterminer le type de chaque opérande (il peut s'agir d'une constante ou d'une variable). Par conséquent, lorsque PHP exécute réellement le code C, différents types d'opérandes seront divisés en différentes fonctions pour un appel direct par la machine virtuelle. Cette partie du code est placée dans

. Le fichier développé est assez volumineux, et nous avons remarqué qu'il existe également un tel code : zend/zend_vm_execute.h

if (IS_CONST == IS_CV) {
Copier après la connexion
Cela n'a aucun sens, n'est-ce pas ? Mais ce n'est pas grave, le compilateur C optimisera et jugera automatiquement de cette façon. Dans la plupart des cas, si l'on veut comprendre la logique d'un certain traitement OPCODE, il est plus facile de lire le fichier modèle

. À propos, le programme qui génère du code C basé sur des modèles est implémenté en PHP. zend/zend_vm_def.h

Processus d'exécution

Pour être précis, l'exécution de PHP est divisée en deux parties : la compilation et l'exécution. Je ne développerai pas ici la partie compilation en détail, mais me concentrerai sur le processus d'exécution.

Après une série de processus de compilation tels que la grammaire et l'analyse lexicale, nous avons obtenu une donnée appelée OPArray, dont la structure est la suivante :

struct _zend_op_array {
	/* Common elements */
	zend_uchar type;
	zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
	uint32_t fn_flags;
	zend_string *function_name;
	zend_class_entry *scope;
	zend_function *prototype;
	uint32_t num_args;
	uint32_t required_num_args;
	zend_arg_info *arg_info;
	/* END of common elements */

	uint32_t *refcount;

	uint32_t last;
	zend_op *opcodes;

	int last_var;
	uint32_t T;
	zend_string **vars;

	int last_live_range;
	int last_try_catch;
	zend_live_range *live_range;
	zend_try_catch_element *try_catch_array;

	/* static variables support */
	HashTable *static_variables;

	zend_string *filename;
	uint32_t line_start;
	uint32_t line_end;
	zend_string *doc_comment;
	uint32_t early_binding; /* the linked list of delayed declarations */

	int last_literal;
	zval *literals;

	int  cache_size;
	void **run_time_cache;

	void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};
Copier après la connexion
Beaucoup de contenu, n'est-ce pas ? Compréhension simple, son essence est un tableau OPCODE plus une collection de données environnementales requises lors de l'exécution. Présentation de plusieurs champs relativement importants :

  • Tableau stockant OPCODE. opcodes

  • Le nom de fichier du script actuellement exécuté. filename

  • Le nom de la méthode actuellement exécutée. function_name

  • Liste de variables statiques. static_variables

  • last_try_catch Dans le contexte actuel, si une exception se produit, try-catch-finally passe aux informations requises. try_catch_array

  • Une collection de tous les littéraux constants tels que la chaîne foo ou le nombre 23. literals

Pourquoi avons-nous besoin de générer des données aussi énormes ? Car plus il y a d’informations générées lors de la compilation, moins il faut de temps lors de l’exécution.

接下来,我们看下 PHP 是如何执行 OPCODE。OPCODE 的执行被放在一个大循环中,这个循环位于 zend/zend_vm_execute.h 中的 execute_ex 函数:

ZEND_API void execute_ex(zend_execute_data *ex)
{
	DCL_OPLINE

	zend_execute_data *execute_data = ex;

	LOAD_OPLINE();
	ZEND_VM_LOOP_INTERRUPT_CHECK();

	while (1) {
		if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) {
			if (EXPECTED(ret > 0)) {
				execute_data = EG(current_execute_data);
				ZEND_VM_LOOP_INTERRUPT_CHECK();
			} else {
				return;
			}
		}
	}

	zend_error_noreturn(E_CORE_ERROR, "Arrived at end of main loop which shouldn&#39;t happen");
}
Copier après la connexion

这里,我去掉了一些环境变量判断分支,保留了运行的主流程。可以看到,在一个无限循环中,虚拟机会不断调用 OPCODE 指定的 handler 函数处理指令集,直到某次指令处理的结果 ret 小于0。注意到,在主流程中并没有移动 OPCODE 数组的当前指针,而是把这个过程放到指令执行的具体函数的结尾。所以,我们在大多数 OPCODE 的实现函数的末尾,都能看到调用这个宏:

ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
Copier après la connexion

在之前那个简单例子中,我们看到 vld 打印出的执行 OPCODE 数组中,最后有一项指令为 ZEND_RETURN 的 OPCODE。但我们编写的 PHP 代码中并没有这样的语句。在编译时期,虚拟机会自动将这个指令加到 OPCODE 数组的结尾。ZEND_RETURN 指令对应的函数会返回 -1,判断执行的结果小于0时,就会退出循环,从而结束程序的运行。

方法调用

如果我们调用一个自定义的函数,虚拟机会如何处理呢?

<?php
function foo() {
    echo &#39;test&#39;;
}

foo();
Copier après la connexion

我们通过 vld 查看生成的 OPCODE。出现了两个 OPCODE 指令执行栈,是因为我们自定义了一个 PHP 函数。在第一个执行栈上,调用自定义函数会执行两个 OPCODE 指令:INIT_FC<a href="http://www.php.cn/wiki/1483.html" target="_blank">ALL</a>DO_FCALL

compiled vars:  none
line     
#* E I O op                           
fetch          
ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   NOP
   6     1        INIT_FCALL                                               &#39;foo&#39;
         2        DO_FCALL                                      
         0
         3      > RETURN                                                   1

compiled vars:  none
line     #* E I O op                           
fetch          
ext  
return  
operands
-------------------------------------------------------------------------------------
   3     0  E >   ECHO                                                     &#39;test&#39;
   4     1      > RETURN                                                   null
Copier après la connexion

其中,INIT_FCALL 准备了执行函数时所需要的上下文数据。DO_FCALL 负责执行函数。DO_FCALL 的处理函数根据不同的调用情况处理了大量逻辑,我摘取了其中执行用户定义的函数的逻辑部分:

ZEND_VM_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL))
{
    USE_OPLINE
    zend_execute_data *call = EX(call);
    zend_function *fbc = call->func;
    zend_object *object;
    zval *ret;

    ...

    if (EXPECTED(fbc->type == ZEND_USER_FUNCTION)) {
        ret = NULL;
        if (RETURN_VALUE_USED(opline)) {
            ret = EX_VAR(opline->result.var);
            ZVAL_NULL(ret);
        }

        call->prev_execute_data = execute_data;
        i_init_func_execute_data(call, &fbc->op_array, ret);

        if (EXPECTED(zend_execute_ex == execute_ex)) {
            ZEND_VM_ENTER();
        } else {
            ZEND_ADD_CALL_FLAG(call, ZEND_CALL_TOP);
            zend_execute_ex(call);
        }
    }

    ...

    ZEND_VM_SET_OPCODE(opline + 1);
    ZEND_VM_CONTINUE();
}
Copier après la connexion

可以看到,DO_FCALL 首先将调用函数前的上下文数据保存到 call->prev_execute_data,然后调用 i_init_func_execute_data 函数,将自定义函数对象中的 op_array(每个自定义函数会在编译的时候生成对应的数据,其数据结构中包含了函数的 OPCODE 数组) 赋值给新的执行上下文对象。

然后,调用 zend_execute_ex 函数,开始执行自定义的函数。zend_execute_ex 实际上就是前面提到的 execute_ex 函数(默认是这样,但扩展可能重写 zend_execute_ex 指针,这个 API 让 PHP 扩展开发者可以通过覆写函数达到扩展功能的目的,不是本篇的主题,不准备深入探讨),只是上下文数据被替换成当前函数所在的上下文数据。

我们可以这样理解,最外层的代码就是一个默认存在的函数(类似 C 语言中的 main()函数),和用户自定义的函数本质上是没有区别的。

逻辑跳转

我们知道指令都是顺序执行的,而我们的程序,一般都包含不少的逻辑判断和循环,这部分又是如何通过 OPCODE 实现的呢?

<?php
$a = 10;
if ($a == 10) {
    echo &#39;success&#39;;
} else {
    echo &#39;failure&#39;;
}
Copier après la connexion

我们还是通过 vld 查看 OPCODE(不得不说 vld 扩展是分析 PHP 的神器)。

compiled vars:  !0 = $a
line     #* E I O op                           
fetch          ext  return  operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, 10
   3     1        IS_EQUAL                                         
   ~2      !0, 10
         2      > JMPZ                                                     ~2, ->5
   4     3    >   ECHO                                                     &#39;success&#39;
         4      > JMP                                                      ->6
   6     5    >   ECHO                                                     &#39;failure&#39;
   7     6    > > RETURN                                                   1
Copier après la connexion

我们看到,JMPZJMP 控制了执行流程。JMP 的逻辑非常简单,将当前的 OPCODE 指针指向需要跳转的 OPCODE。

ZEND_VM_HANDLER(42, ZEND_JMP, JMP_ADDR, ANY)
{
	USE_OPLINE

	ZEND_VM_SET_OPCODE(OP_JMP_ADDR(opline, opline->op1));
	ZEND_VM_CONTINUE();
}
Copier après la connexion

JMPZ 仅仅是多了一次判断,根据结果选择是否跳转,这里就不再重复列举了。而处理循环的方式与判断基本上是类似的。

<?php
$a = [1, 2, 3];
foreach ($a as $n) {
    echo $n;
}
Copier après la connexion
compiled vars:  !0 = $a, !1 = $n
line     #* E I O op                           
fetch          
ext  return  
operands
-------------------------------------------------------------------------------------
   2     0  E >   ASSIGN                                                   !0, <array>
   3     1      > FE_RESET_R                                       
   $3      !0, ->5
         2    > > FE_FETCH_R                                               $3, !1, ->5
   4     3    >   ECHO                                                     !1
         4      > JMP                                                      ->2
         5    >   FE_FREE                                                  $3
   5     6      > RETURN                                                   1
Copier après la connexion

循环只需要 JMP 指令即可完成,通过 FE_FETCH_R 指令判断是否已经到达数组的结尾,如果到达则退出循环。

结语

通过了解 Zend 虚拟机,相信你对 PHP 是如何运行的,会有更深刻的理解。想到我们写的一行行代码,最后机器执行的时候会变成数不胜数的指令,每个指令又建立在复杂的处理逻辑之上。那些从前随意写下的代码,现在会不会在脑海里不自觉的转换成 OPCODE 再品味一番呢?

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!

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

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

AI Hentai Generator

AI Hentai Generator

Générez AI Hentai gratuitement.

Article chaud

R.E.P.O. Crystals d'énergie expliqués et ce qu'ils font (cristal jaune)
1 Il y a quelques mois By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Meilleurs paramètres graphiques
1 Il y a quelques mois By 尊渡假赌尊渡假赌尊渡假赌
Will R.E.P.O. Vous avez un jeu croisé?
1 Il y a quelques mois By 尊渡假赌尊渡假赌尊渡假赌

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Guide d'installation et de mise à niveau de PHP 8.4 pour Ubuntu et Debian Guide d'installation et de mise à niveau de PHP 8.4 pour Ubuntu et Debian Dec 24, 2024 pm 04:42 PM

PHP 8.4 apporte plusieurs nouvelles fonctionnalités, améliorations de sécurité et de performances avec une bonne quantité de dépréciations et de suppressions de fonctionnalités. Ce guide explique comment installer PHP 8.4 ou mettre à niveau vers PHP 8.4 sur Ubuntu, Debian ou leurs dérivés. Bien qu'il soit possible de compiler PHP à partir des sources, son installation à partir d'un référentiel APT comme expliqué ci-dessous est souvent plus rapide et plus sécurisée car ces référentiels fourniront les dernières corrections de bogues et mises à jour de sécurité à l'avenir.

Comment configurer Visual Studio Code (VS Code) pour le développement PHP Comment configurer Visual Studio Code (VS Code) pour le développement PHP Dec 20, 2024 am 11:31 AM

Visual Studio Code, également connu sous le nom de VS Code, est un éditeur de code source gratuit – ou environnement de développement intégré (IDE) – disponible pour tous les principaux systèmes d'exploitation. Avec une large collection d'extensions pour de nombreux langages de programmation, VS Code peut être c

7 fonctions PHP que je regrette de ne pas connaître auparavant 7 fonctions PHP que je regrette de ne pas connaître auparavant Nov 13, 2024 am 09:42 AM

Si vous êtes un développeur PHP expérimenté, vous aurez peut-être le sentiment d'y être déjà allé et de l'avoir déjà fait. Vous avez développé un nombre important d'applications, débogué des millions de lignes de code et peaufiné de nombreux scripts pour réaliser des opérations.

Comment analysez-vous et traitez-vous HTML / XML dans PHP? Comment analysez-vous et traitez-vous HTML / XML dans PHP? Feb 07, 2025 am 11:57 AM

Ce tutoriel montre comment traiter efficacement les documents XML à l'aide de PHP. XML (Language de balisage extensible) est un langage de balisage basé sur le texte polyvalent conçu à la fois pour la lisibilité humaine et l'analyse de la machine. Il est couramment utilisé pour le stockage de données et

Expliquez les jetons Web JSON (JWT) et leur cas d'utilisation dans les API PHP. Expliquez les jetons Web JSON (JWT) et leur cas d'utilisation dans les API PHP. Apr 05, 2025 am 12:04 AM

JWT est une norme ouverte basée sur JSON, utilisée pour transmettre en toute sécurité des informations entre les parties, principalement pour l'authentification de l'identité et l'échange d'informations. 1. JWT se compose de trois parties: en-tête, charge utile et signature. 2. Le principe de travail de JWT comprend trois étapes: la génération de JWT, la vérification de la charge utile JWT et l'analyse. 3. Lorsque vous utilisez JWT pour l'authentification en PHP, JWT peut être généré et vérifié, et les informations sur le rôle et l'autorisation des utilisateurs peuvent être incluses dans l'utilisation avancée. 4. Les erreurs courantes incluent une défaillance de vérification de signature, l'expiration des jetons et la charge utile surdimensionnée. Les compétences de débogage incluent l'utilisation des outils de débogage et de l'exploitation forestière. 5. L'optimisation des performances et les meilleures pratiques incluent l'utilisation des algorithmes de signature appropriés, la définition des périodes de validité raisonnablement,

Programme PHP pour compter les voyelles dans une chaîne Programme PHP pour compter les voyelles dans une chaîne Feb 07, 2025 pm 12:12 PM

Une chaîne est une séquence de caractères, y compris des lettres, des nombres et des symboles. Ce tutoriel apprendra à calculer le nombre de voyelles dans une chaîne donnée en PHP en utilisant différentes méthodes. Les voyelles en anglais sont a, e, i, o, u, et elles peuvent être en majuscules ou en minuscules. Qu'est-ce qu'une voyelle? Les voyelles sont des caractères alphabétiques qui représentent une prononciation spécifique. Il y a cinq voyelles en anglais, y compris les majuscules et les minuscules: a, e, i, o, u Exemple 1 Entrée: String = "TutorialSpoint" Sortie: 6 expliquer Les voyelles dans la chaîne "TutorialSpoint" sont u, o, i, a, o, i. Il y a 6 yuans au total

Expliquez la liaison statique tardive en PHP (statique: :). Expliquez la liaison statique tardive en PHP (statique: :). Apr 03, 2025 am 12:04 AM

Liaison statique (statique: :) ​​implémente la liaison statique tardive (LSB) dans PHP, permettant à des classes d'appel d'être référencées dans des contextes statiques plutôt que de définir des classes. 1) Le processus d'analyse est effectué au moment de l'exécution, 2) Recherchez la classe d'appel dans la relation de succession, 3) il peut apporter des frais généraux de performance.

Quelles sont les méthodes PHP Magic (__construct, __ destruct, __ call, __get, __set, etc.) et fournir des cas d'utilisation? Quelles sont les méthodes PHP Magic (__construct, __ destruct, __ call, __get, __set, etc.) et fournir des cas d'utilisation? Apr 03, 2025 am 12:03 AM

Quelles sont les méthodes magiques de PHP? Les méthodes magiques de PHP incluent: 1. \ _ \ _ Construct, utilisé pour initialiser les objets; 2. \ _ \ _ Destruct, utilisé pour nettoyer les ressources; 3. \ _ \ _ Appel, gérer les appels de méthode inexistants; 4. \ _ \ _ GET, Implémentez l'accès à l'attribut dynamique; 5. \ _ \ _ SET, Implémentez les paramètres d'attribut dynamique. Ces méthodes sont automatiquement appelées dans certaines situations, améliorant la flexibilité et l'efficacité du code.

See all articles