Maison >développement back-end >tutoriel php >Explication graphique détaillée du code du mécanisme de stockage du noyau PHP (séparation/changement)

Explication graphique détaillée du code du mécanisme de stockage du noyau PHP (séparation/changement)

黄舟
黄舟original
2017-03-09 09:27:091386parcourir

Avant-propos :

La plupart des programmeurs qui lisent des blogs n'aiment peut-être pas lire des articles contenant beaucoup de caractères chinois, mais cet article présente des descriptions basées sur le chinois. personnages. , si vous le lisez patiemment, ce sera certainement gratifiant pour la plupart des gens !

Peut-être que vous le savez, peut-être que vous ne le savez pas, PHP est un langage de script dynamique, faiblement typé. Le type dit faible signifie que PHP ne vérifie pas strictement les types de variables (à proprement parler, PHP est un langage moyennement typé). Lors de la déclaration d'une variable, il n'a pas besoin d'indiquer explicitement le type de données qu'il enregistre. Par exemple : $a = 1; (Entier) $a ="1"; (Chaîne)

J'ai toujours utilisé PHP, mais de quoi s'agit-il exactement et comment l'implémentation sous-jacente est-elle réalisée pour faire de PHP un langage aussi pratique et rapide, faiblement typé ?

J'ai récemment lu beaucoup de livres et d'informations de blog connexes, et j'ai beaucoup appris sur les mécanismes du PHP noyau.
Une compréhension simple de PHP est une bibliothèque de classes en langage C Si vous allez sur php.net et téléchargez son code source, vous trouverez tout d'abord le noyau. de php est le moteur zend, c'est une bibliothèque de fonctions écrite en langage C, utilisée pour gérer la gestion des fonctions sous-jacentes, la gestion de la mémoire, la gestion des classes et la gestion des variables. Sur le noyau, ils ont écrit de nombreuses extensions, dont la plupart sont indépendantes. Pour utiliser une métaphore du système d'exploitation, zend Engine est un système d'exploitation, et il fournit officiellement de nombreuses "applications", mais cette "application" n'est pas Media Play mais MySQL, libxml et dom. Bien entendu, vous pouvez également développer vos propres extensions basées sur l'API du moteur zend.


Commençons par introduire le mécanisme de stockage des variables PHP dans le noyau.

PHP est un langage typé, ce qui signifie qu'une variable PHP peut enregistrer n'importe quel type de données. Mais PHP est écrit en langage C, et le langage C est un langage fortement typé, n'est-ce pas ? Chaque variable aura un type fixe (l'une d'elles est convertie en type fort, mais il peut y avoir des problèmes), alors comment le faire dans le moteur Zend ? pour enregistrer n'importe quel type de données dans une variable ? Veuillez consulter sa structure de stockage ci-dessous.

Ouvrez le fichier d'en-tête Zend/zend.h et vous trouverez la structure suivante Zval :


Structure 1.zval

 typedef struct _zval_struct zval;
 typedef union _zvalue_value {
    long lval;      /* long value */
    double dval;    /* double value */
    struct {
    char *val; //4字节
    int len;   //4字节
    } str;
    HashTable *ht;    /* hash table value */
    zend_object_value obj;
 } zvalue_value;


 struct _zval_struct {
    /* Variable information */
    zvalue_value value;  /* 变量值保存在这里 12字节*/
    zend_uint refcount;//4字节,变量引用计数器
    zend_uchar type;   /* active type变量类型 1字节*/
    zend_uchar is_ref;//是否变量被&引用,0表示非引用,1表示引用,1字节
    };


2.zend_uchar tapez

Les variables en PHP incluent quatre types scalaires (bool, int, float, string), deux A composites tapez (tableau, objet) et deux types spéciaux (ressource et NULL). Au sein de zend, ces types correspondent aux macros suivantes (emplacement du code phpsrc/Zend/zend.h) Zend détermine à quel membre de valeur accéder en fonction de la valeur du type. Les valeurs disponibles sont les suivantes :


3
.zend_uint refcount__gc

Cette valeur est en fait un compteur pour enregistrer le nombre de variables (ou symboles, symboles, tous les symboles existent dans la table des symboles (symbole table), différentes portées utilisent différentes tables de symboles, nous en discuterons plus tard) pointe vers le zval. Lorsque la variable est générée, son refcount=1. Les opérations d'affectation typiques telles que $a = $b augmenteront le refcount de zval de 1, et l'opération de non-définition le diminuera de 1 en conséquence. Avant PHP5.3, le mécanisme de comptage de références était utilisé pour implémenter GC Si le refcount d'un zval était inférieur à 0, le moteur Zend penserait qu'il n'y avait aucune variable pointant vers le zval, et libérerait donc l'espace mémoire occupé par. le zval. Mais parfois, les choses ne sont pas si simples. Nous verrons plus tard que Le simple mécanisme de comptage de références ne peut pas GC sortir le zval de référence circulaire (voir l'exemple 3 ci-dessous pour plus de détails), même si la variable pointant au zval a été désinitialisé, ce qui a entraîné une fuite de mémoire (Memory Fuite).

4.is_ref__gc
.

Ce champ est utilisé pour marquer si la variable est une variable de référence . Pour les variables ordinaires, la valeur est 0 et pour les variables de référence, la valeur est 1. Cette variable affectera le partage, la séparation, etc. de zval. Nous en discuterons plus tard.

Comme son nom l'indique, ref_count__gc et is_ref__gc sont deux champs très importants requis par le mécanisme GC de PHP. Les valeurs de ces deux champs peuvent être déboguées via xdebug, etc. Afficher les outils.

Ensuite, nous nous concentrerons sur zval pour décrire quel est le mécanisme de stockage des variables PHP.

J'ai également introduit l'installation de Xdebug dans le débogage PHPstorm Xdebug auparavant. Je n'entrerai pas dans les détails ici. Veuillez consulter : débogage du point d'arrêt phpstorm Xdebug PHP

.

安装成功后,你的脚本中,可以通过xdebug_debug_zval打印Zval的信息,用法:

 $var = 1;
 debug_zval_dump($var);
 $var_dup = $var;
 debug_zval_dump($var);

实例一:

    $a = 1;
    $b = $a;
    $c = $b;
    $d = &$c; // 在一堆非引用赋值中,插入一个引用

  整个过程图示如下:

---------------------------------------------------------

实例二:

   $a = 1;
    $b = &$a;
    $c = &$b;
    $d = $c; // 在一堆引用赋值中,插入一个非引用

  整个过程图示如下:



通过实例一、二,展现了,这就是PHP的copy on write写时分离机制change on write写时改变机制

过程:

PHP在修改一个变量以前,会首先查看这个变量的refcount,如果refcount大于1,PHP就会执行一个分离的例程, 

     对于上面的实例一代码,当执行到第四行的时候,PHP发现$c指向的zval的refcount大于1,那么PHP就会复制一个新的zval出来,将原zval的refcount减1,并修改symbol_table,使得$a,$b和$c分离(Separation)。这个机制就是所谓的copy on write(写时复制/写时分离)。把$d指向的新zval的is_ref的值 == 1 ,这个机制叫做change on write(写时改变)

结论:

分离指的是:分离两个变量存储的zval的位置,让分开不指向同一个空间! (那如何判定是否要分离呢,依据是什么?见下边)

改变指的是,有&引用赋值时,要把新开辟的zval 的 is_ref 赋值为1


判定是否分离的条件:如果is_ref =1 或recount == 1,则不分离

if((*val)->is_ref || (*val)->refcount<2){
          //不执行Separation
        ... ;//process
  }

---------------------------------------------------------------------------------------------------

实例三:(内存是如何泄漏的)

数组变量与普通变量生成的zval非常类似,但也有很大不同

举例:


$a = $array(&#39;one&#39;);  
$a[] = &$a;  
xdebug_debug_zval(&#39;a&#39;);


debug_zval_dump打印出zval的结构是:

a: (refcount=2, is_ref=1)=array (
    0 => (refcount=1, is_ref=0)=&#39;one&#39;, 
    1 => (refcount=2, is_ref=1)=...
)


上述输出中,…表示指向原始数组,因而这是一个循环的引用。如下图所示:




现在,我们对$a执行unset操作,这会在symbol table中删除相应的symbol,同时,zval的refcount减1(之前为2),也就是说,现在的zval应该是这样的结构:

unset($a);
(refcount=1, is_ref=1)=array (
    0 => (refcount=1, is_ref=0)=&#39;one&#39;, 
    1 => (refcount=1, is_ref=1)=...
)


(应该ref_count=1)


(unset,其实就是打断$a在 符号表(symble table) 与zval 的一个指针映射关系。)


这时,不幸的事情发生了!

  Unset之后,虽然没有变量指向该zval,但是该zval却不能被GC(指PHP5.3之前的单纯引用计数机制的GC)清理掉,$a 被释放,但是$a里的$a[1]也指向了该zval,它没有被释放,导致zval的refcount均大于0。这样,这些zval实际上会一直存在内存中,直到请求结束(参考SAPI的生命周期)。在此之前,这些zval占据的内存不能被使用,便白白浪费了,换句话说,无法释放的内存导致了内存泄露

Si ce type de fuite de mémoire ne se produit qu'une ou plusieurs fois, ce n'est pas grave, mais s'il s'agit de milliers de fuites de mémoire, c'est un gros problème. Surtout dans les scripts de longue durée (tels que les démons, qui s'exécutent toujours en arrière-plan sans interruption), puisque la mémoire ne peut pas être recyclée, le système finira par "n'avoir plus de mémoire disponible", cette opération doit donc être évitée.

Mécanisme de récupération de place :

1.php utilise à l'origine un compteur de référence pour réaliser le recyclage de la mémoire, c'est-à-dire que plusieurs variables php peuvent référencer la même mémoire. Dans ce cas, désactivez la suppression. l'un d'eux ne libérera pas la mémoire
Par exemple : $a = 1; $b = $a; unset($a);//$a开辟的内存不会回收
2. Après avoir quitté la portée de la variable, la mémoire occupée par la variable le sera. be Nettoyage automatique (n'inclut pas les variables statiques, les variables statiques sont créées lorsque le script est chargé et libérées à la fin du script),

Par exemple, si vous supprimez des variables locales dans une fonction ou une méthode, la mémoire ne sera pas réduite lorsqu'elle est affichée en dehors de la fonction.

3. Il y a un défaut dans le comptage des références, c'est-à-dire que lorsqu'une référence circulaire se produit, le compteur ne peut pas être remis à 0. L'utilisation de la mémoire continuera jusqu'à la fin. de l'accès à la page.

Pour ce problème, un mécanisme de garbage collection a été ajouté dans PHP5.3. Pour plus de détails, veuillez vous référer à la documentation : http://www.php.cn/

Le mécanisme de collecte des ordures a été proposé pour la première fois en Lisp. Informations sur la collecte.


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:
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