Maison >développement back-end >tutoriel php >Gestionnaire de mémoire Zend pour php

Gestionnaire de mémoire Zend pour php

coldplay.xixi
coldplay.xixiavant
2020-07-29 17:14:152466parcourir

Gestionnaire de mémoire Zend pour php

Zend Memory Manager

Zend Memory Manager, souvent abrégé en ZendMM ou ZMM, est une couche C conçu pour offrir la possibilité d'allouer et de libérer de la mémoire dynamique liée aux requêtes .

Faites attention à « demande de liaison » dans la phrase ci-dessus.

ZendMM n'est pas simplement une couche classique au-dessus de l'allocateur de mémoire dynamique de la libc, principalement représentée par deux appels API malloc()/free(). ZendMM concerne la mémoire liée aux requêtes que PHP doit allouer lors du traitement d'une requête.

Recommandations d'apprentissage associées : Programmation PHP de l'entrée à la maîtrise

Deux principaux pools de mémoire dynamique en PHP

PHP C'est une architecture du rien partagé. Eh bien, pas à 100 %. Nous vous expliquons plus d'informations sur les étapes et les cycles.

PHP peut gérer des centaines ou des milliers de requêtes dans le même processus. Par défaut, PHP oubliera toute information sur la requête en cours une fois celle-ci terminée.

Le message "oublier" est interprété comme libérant les tampons dynamiques alloués lors du traitement de la requête. Cela signifie que vous ne pouvez pas utiliser les appels libc traditionnels pour allouer de la mémoire dynamique lors du traitement d'une requête. C'est parfaitement valable, mais vous vous donnez la possibilité d'oublier de libérer le buffer.

ZendMM est livré avec une API qui remplace l'allocateur dynamique de la libc en copiant son API. Les programmeurs doivent utiliser cette API au lieu de l'allocateur de la libc lors du traitement des requêtes.

Par exemple, lorsque PHP traite une requête, il analysera le fichier PHP. Par exemple, celles-ci donneront lieu à des déclarations de fonctions et de classes. Lorsque le compilateur commence à compiler un fichier PHP, il alloue de la mémoire dynamique pour stocker les classes et fonctions qu'il découvre. Cependant, PHP les publie à la fin de la requête. Par défaut, PHP oublie

beaucoup d'

informations d'une requête à l'autre.

Cependant, il existe des informations très rares dont vous avez besoin pour persister dans plusieurs requêtes. Mais ce n'est pas courant.

Qu'est-ce qui peut rester inchangé par les demandes ? Ce que nous appelons un objet persistant

. Encore une fois : ce n’est pas une situation courante. Par exemple, le chemin actuel de l'exécutable PHP ne change pas entre les requêtes. Ses informations sont allouées de manière permanente, ce qui signifie qu'il appelle les libc traditionnelles

pour les allouer.

Quoi d'autre ? une certaine ficelle. Par exemple, la chaîne "_SERVER" sera réutilisée entre les requêtes car un tableau malloc () PHP sera créé pour chaque requête. Ainsi, la chaîne

"_SERVER"

elle-même peut être allouée de manière permanente, puisqu'elle ne sera allouée qu'une seule fois. Vous devez vous rappeler :$_SERVER

Lors de l'écriture du noyau ou des extensions PHP, il existe deux méthodes d'allocation dynamique de mémoire :

    Demande de liaison déterminée allocation dynamique.
  • Allocation dynamique permanente.

    • L'allocation dynamique de mémoire liée à la requête
  • n'est effectuée que lorsque PHP gère la requête (pas avant ni après).
  • Ne doit être effectué qu'à l'aide de l'API d'allocation dynamique de mémoire ZendMM.

      est très courant dans la conception d'extensions, en gros, 95 % des allocations dynamiques sont des liaisons de requêtes.
    • Suivi par ZendMM et vous informera des fuites.
    • L'allocation dynamique permanente de mémoire
  • ne doit pas être effectuée pendant que PHP traite la requête (ce n'est pas interdit, mais c'est une mauvaise idée) .
  • Ne sera pas suivi par ZendMM et vous ne serez pas averti des fuites.

      devrait être rare dans les extensions.
    • Gardez également à l'esprit que tout le code source PHP est basé sur ce niveau de mémoire. Par conséquent, de nombreuses structures internes sont allouées à l'aide du gestionnaire de mémoire Zend. La plupart appellent une API « persistante » qui, une fois appelée, entraînera une allocation libc traditionnelle.
    Il s'agit d'une allocation liée à la requête zend_string :
  • zend_string *foo = zend_string_init("foo", strlen("foo"), 0);
Il s'agit d'une allocation persistante :

zend_string *foo = zend_string_init("foo", strlen("foo"), 1);

La même HashTable.

Demande d'allocation liée :

zend_array ar;
zend_hash_init(&ar, 8, NULL, NULL, 0);

Allocation persistante :

zend_array ar;
zend_hash_init(&ar, 8, NULL, NULL, 1);

C'est toujours la même dans toutes les différentes API Zend. Généralement passé en dernier paramètre,
"0"

signifie "Je veux allouer cette structure en utilisant ZendMM, donc demander une liaison", ou

"1"

signifie "Je veux utiliser ZendMM pour allouer cette structure, donc la liaison est demandée", ou

"1" signifie "Je veux utiliser ZendMM pour allouer cette structure, donc la liaison est demandée" Appelez les libc traditionnelles pour allouer cette structure ". Apparemment, ces structures fournissent une API qui se souvient de la façon dont elles ont alloué la structure afin que la fonction de désallocation correcte soit utilisée lors de sa destruction. Donc dans un code comme celui-ci : malloc()

zend_string_release(foo);
zend_hash_destroy(&ar);

API sait si ces structures sont allouées à l'aide de la liaison de requête ou allouées de manière permanente, le premier cas utilisera

pour la libérer, le deuxième cas la libc

.

API Zend Memory Manager

L'API se trouve dans Zend/zend_alloc.h

L'API est principalement constituée de macros C, pas de fonctions, donc si vous les déboguez et souhaitez comprendre leur fonctionnement Principe, soyez prêt. Ces API copient les fonctions de la libc, souvent avec un « e » ajouté au nom de la fonction ; vous ne devriez donc pas commettre d'erreur, il n'y a pas beaucoup de détails sur l'API ;

En gros, ceux que vous utiliserez le plus souvent sont emalloc(size_t) et efree(void *).

fournit également ecalloc(size_t nmemb,size_t size), qui alloue un seul size de taille nmemb et met à zéro la région. Si vous êtes un programmeur C expérimenté, sachez que dans la mesure du possible, il est préférable d'utiliser emalloc() plutôt que ecalloc() car ecalloc() mettra à zéro la zone mémoire, ce qui est important dans la détection des erreurs de pointeur. aide. Gardez à l'esprit que emalloc() fonctionne fondamentalement de la même manière que la libc malloc() : elle recherchera une zone suffisamment grande dans différentes piscines et vous donnera l'espace le plus approprié. Par conséquent, vous risquez de vous retrouver avec un pointeur récupéré.

puis safe_emalloc(size_t nmemb,size_t size,size_t offset), qui est emalloc(size * nmemb + offset), mais il vérifie le débordement pour vous. Cet appel API doit être utilisé si le numéro qui doit être fourni provient d'une source non fiable (telle que l'espace utilisateur).

Concernant les chaînes, estrdup(char *) et estrndup(char *, size_t len) permettent de copier des chaînes ou des chaînes binaires.

Peu importe ce qui arrive, le pointeur renvoyé par ZendMM doit être libéré en appelant le efree() de ZendMM, et n'est pas le free() de la libc.

Remarque

sur l'allocation persistante. Les allocations durables restent valables entre les demandes. Vous utilisez généralement la libc habituelle malloc/ free pour ce faire, mais ZendMM a quelques raccourcis pour l'allocateur libc : l'API "persistante". L'API commence par des lettres "p" et vous permet de choisir entre une allocation ZendMM ou une allocation persistante. Par conséquent, pemalloc(size_t, 1) est juste malloc(), pefree(void *, 1) est free() et pestrdup(void *, 1) est strdup(). Je dis juste.

Zend Memory Manager Debug Shield

ZendMM fournit les fonctionnalités suivantes :

  • Gestion de la consommation mémoire.
  • Suivi des fuites de mémoire et libération automatique.
  • Accélérez les allocations en pré-attribuant des tampons de tailles connues et en gardant les caches chauds inactifs

Gestion de la consommation de mémoire

ZendMM est un espace utilisateur PHP sous-jacent au " fonction "limite_mémoire". Chaque octet alloué à l'aide de la couche ZendMM est compté et additionné. Vous savez ce qui se passe lorsque la memory_limit de l'INI est atteinte. Cela signifie également que toutes les allocations effectuées via ZendMM sont reflétées dans memory_get_usage() dans l'espace utilisateur PHP.

En tant que développeur d'extensions, c'est une bonne chose car cela permet de suivre la taille du tas de votre processus PHP.

Si une erreur de limite de mémoire est initiée, le moteur sortira de la position actuelle du code dans le bloc de capture, puis se terminera gracieusement. Mais il est impossible de revenir à un emplacement de code dépassant la limite. Vous devez être préparé à cela.

Théoriquement, cela signifie que ZendMM ne peut pas vous renvoyer un pointeur NULL. Si l'allocation du système d'exploitation échoue ou si l'allocation produit une erreur de limite de mémoire, le code s'exécutera dans un bloc catch et ne reviendra pas à votre appel d'allocation.

Si pour une raison quelconque vous devez contourner cette protection, vous devez utiliser les appels libc traditionnels, tels que malloc(). Dans tous les cas, soyez prudent et sachez ce que vous faites. Si vous utilisez ZendMM, vous devrez peut-être allouer une grande quantité de mémoire et dépasser la memory_limit de PHP. Utilisez donc un autre allocateur (comme la libc), mais attention : votre extension augmentera la taille actuelle du tas du processus. memory_get_usage() n'est pas visible en PHP, mais le tas actuel peut être analysé à l'aide des fonctionnalités du système d'exploitation (telles que /proc/{pid}/maps)

Remarque

Si vous devez désactiver complètement ZendMM, vous pouvez démarrer PHP en utilisant la variable d'environnement USE_ZEND_ALLOC = 0. De cette façon, chaque appel à l'API ZendMM (comme emalloc()) sera dirigé vers l'appel libc et ZendMM sera désactivé. Ceci est particulièrement utile dans le cas du débogage de la mémoire.

Traçage des fuites de mémoire

Rappelez-vous la règle principale de ZendMM : il démarre lorsque la requête démarre, puis s'attend à ce que vous appeliez son API lorsque vous avez besoin de mémoire dynamique pour gérer la requête. Lorsque la requête en cours se termine, ZendMM s'arrête.

En fermant, il parcourra tous ses pointeurs en direct et si vous utilisez la version de débogage de PHP, il vous avertira des fuites de mémoire.

Expliquons-le un peu plus clairement : Si à la fin de la requête en cours, ZendMM trouve des blocs mémoire actifs, cela signifie que ces blocs mémoire fuient. Il ne devrait y avoir aucun bloc de mémoire actif sur le tas ZendMM à la fin de la requête, car celui qui a alloué de la mémoire aurait dû la libérer.

如果您忘记释放块,它们将全部显示在 stderr上。此内存泄漏报告进程仅在以下情况下有效:

  • 你正在使用 PHP 的调试构建
  • 在 php.ini 中具有 report_memleaks = On(默认)

这是一个简单泄漏到扩展中的示例:

PHP_RINIT_FUNCTION(example)
{
    void *foo = emalloc(128);
}

在启动该扩展的情况下启动 PHP,在调试版本上会在 stderr 上生成:

[Fri Jun 9 16:04:59 2017]  Script:  '/tmp/foobar.php'
/path/to/extension/file.c(123) : Freeing 0x00007fffeee65000 (128 bytes), script=/tmp/foobar.php
=== Total 1 memory leaks detected ===

当 Zend 内存管理器关闭时,在每个已处理请求的末尾,将生成这些行。

但是要当心:

  • 显然,ZendMM 对持久分配或以不同于使用持久分配的方式执行的分配一无所知。因此,ZendMM 只能警告你有关它知道的分配信息,在这里不会报告每个传统的 libc 分配信息。
  • 如果 PHP 以错误的方式关闭(我们称之为不正常关闭),ZendMM 将报告大量泄漏。这是因为引擎在错误关闭时会使用longjmp()调用 catch 块,防止清理所有内存的代码运行。因此,许多泄漏得到报告。尤其是在调用 PHP 的 exit()/ die()之后,或者在 PHP 的某些关键部分触发了致命错误时,就会发生这种情况。
  • 如果你使用非调试版本的 PHP,则 stderr 上不会显示任何内容,ZendMM 是愚蠢的,但仍会清除程序员尚未明确释放的所有分配的请求绑定缓冲区

你必须记住的是 ZendMM 泄漏跟踪是一个不错的奖励工具,但它不能代替真正的 C 内存调试器。

ZendMM 内部设计

常见错误和错误

这是使用 ZendMM 时最常见的错误,以及你应该怎么做。

  1. 不处理请求时使用 ZendMM。

获取有关 PHP 生命周期的信息,以了解在扩展中何时处理请求,何时不处理。如果在请求范围之外使用 ZendMM(例如在MINIT()中),在处理第一个请求之前,ZendMM 会静默清除分配,并且可能会使用after-after-free:根本没有。

  1. 缓冲区上溢和下溢。

使用内存调试器。如果你在 ZendMM 返回的内存区域以下或过去写入内容,则将覆盖关键的 ZendMM 结构并触发崩溃。如果 ZendMM 能够为你检测到混乱,则可能会显示“zend_mm_heap损坏”的消息。堆栈追踪将显示从某些代码到某些 ZendMM 代码的崩溃。ZendMM 代码不会自行崩溃。如果你在 ZendMM 代码中间崩溃,那很可能意味着你在某个地方弄乱了指针。插入你喜欢的内存调试器,查找有罪的部分并进行修复。

  1. 混合 API 调用

如果分配一个 ZendMM 指针(即emalloc())并使用 libc 释放它(free()),或相反的情况:你将崩溃。要严谨对待。另外,如果你将其不知道的任何指针传递给 ZendMM 的efree():将会崩溃。

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:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer