PHP用のZendメモリマネージャー

coldplay.xixi
リリース: 2023-04-09 11:54:02
転載
2395 人が閲覧しました

PHP用のZendメモリマネージャー

Zend Memory Manager

Zend Memory Manager は、ZendMM または ZMM と略されることが多く、C レイヤーです。動的 リクエスト バインド メモリを割り当ておよび解放する機能を提供するように設計されています。

上記の文の「リクエスト バインディング」に注目してください。

ZendMM は、主に 2 つの API 呼び出し malloc()/free() で表される、libc の動的メモリ アロケーター上の単なる従来の層ではありません。 ZendMM は、リクエストの処理時に PHP が割り当てる必要があるリクエスト バインド メモリに関するものです。

関連する学習の推奨事項: PHP プログラミングの入門から熟練度まで

PHP の 2 つの主要な動的メモリ プール

PHP何も共有しないアーキテクチャ。そうですね、100% ではありません。ステップとサイクルについて詳しく説明します。

PHP は、同じプロセスで数百または数千のリクエストを処理できます。デフォルトでは、PHP は現在のリクエストが完了すると、そのリクエストに関する情報をすべて忘れます。

「忘れた」メッセージは、リクエストの処理中に割り当てられた動的バッファを解放すると解釈されます。これは、リクエストの処理中に、従来の libc 呼び出しを使用して動的メモリを割り当てることができないことを意味します。これは完全に有効ですが、バッファを解放するのを忘れる可能性があります。

ZendMM には、libc の API をコピーすることで libc の動的アロケーターを置き換える API が付属しています。プログラマはリクエストを処理するときに libc のアロケータの代わりにこの API を使用する必要があります。

たとえば、PHP はリクエストを処理するときに、PHP ファイルを解析します。たとえば、これらは関数やクラスの宣言になります。コンパイラは、PHP ファイルのコンパイルを開始すると、検出したクラスと関数を保存するために動的メモリを割り当てます。ただし、PHP はリクエストの最後にこれらを解放します。デフォルトでは、PHP はリクエスト間で

多くの

情報を忘れます。

ただし、非常にまれな情報の中には、複数のリクエストにわたって保持する必要があるものもあります。しかし、これは一般的ではありません。

リクエストによって変更されないものは何ですか? 永続

オブジェクトと呼ばれるもの。繰り返しますが、これは一般的な状況ではありません。たとえば、現在の PHP 実行可能パスはリクエスト間で変更されません。その情報は永続的に割り当てられます。つまり、従来の libc の

malloc ()

を呼び出して情報を割り当てます。 ######ほかに何か?何かの文字列。たとえば、

$_SERVER PHP 配列がリクエストごとに作成されるため、"_SERVER" 文字列はリクエスト間で再利用されます。したがって、"_SERVER"

文字列自体は一度だけ割り当てられるため、永続的に割り当てることができます。

覚えておく必要があります:PHP コアまたは拡張機能を作成する場合、動的メモリ割り当てには 2 つの方法があります:

リクエスト バインディングの決定動的割り当て。

    永続的な動的割り当て。
    • リクエスト バインディングの動的メモリ割り当て
    • は、PHP がリクエストを処理するときにのみ実行されます (前後ではありません)。
  • ZendMM 動的メモリ割り当て API を使用してのみ実行する必要があります。
  • は拡張機能の設計では非常に一般的で、基本的に動的割り当ての 95% はリクエスト バインディングです。

      は ZendMM によって追跡されており、リークについて通知されます。
    • 永続的な動的メモリ割り当て
    • は、PHP がリクエストを処理している間は実行しないでください (禁止されているわけではありませんが、悪い考えです)。
  • は ZendMM によって追跡されず、リークについては通知されません。
  • は拡張機能ではまれです。

    • また、すべての PHP ソース コードはこのメモリ レベルに基づいていることに注意してください。したがって、多くの内部構造は Zend メモリ マネージャーを使用して割り当てられます。ほとんどの場合、「永続的な」API を呼び出します。呼び出されると、従来の libc 割り当てが行われます。
    • これはリクエストバインドされた割り当てです zend_string:
    • zend_string *foo = zend_string_init("foo", strlen("foo"), 0);
      ログイン後にコピー
    これは永続的な割り当てです:
  • zend_string *foo = zend_string_init("foo", strlen("foo"), 1);
    ログイン後にコピー
同じハッシュテーブル。

リクエスト限定割り当て:

zend_array ar;
zend_hash_init(&ar, 8, NULL, NULL, 0);
ログイン後にコピー

永続的割り当て:

zend_array ar;
zend_hash_init(&ar, 8, NULL, NULL, 1);
ログイン後にコピー

これは、すべての異なる Zend API で常に同じです。通常は最後のパラメータとして渡されます。

"0"

は「ZendMM を使用してこの構造体を割り当てたいので、バインディングを要求する」を意味し、
"1"

は「ZendMM を使用したい」ことを意味します「この構造体を割り当てるためにバインディングが要求される」、または

"1"

は「ZendMM を使用してこの構造体を割り当てるため、バインディングが要求される」ことを意味します 従来の libc の

malloc() を呼び出します この構造を割り当てます。」 どうやら、これらの構造体は、構造体がどのように割り当てられたかを記憶する API を提供しており、破棄されたときに正しい解放関数が使用されるようです。したがって、次のようなコードになります:

zend_string_release(foo);
zend_hash_destroy(&ar);
ログイン後にコピー
API は、これらの構造がリクエスト バインディングを使用して割り当てられているか、永続的に割り当てられているかを認識します。前者の場合は efree()

を使用して解放され、2 番目の場合は解放されます。最初の状況は libc の

free()

です。

Zend Memory Manager API

API は Zend/zend_alloc.h にあります

API は主に C マクロであり、関数ではないため、デバッグしてその動作を理解したい場合は、原則として、ご準備ください。これらの API は libc の関数をコピーしており、多くの場合関数名に「e」が追加されています。したがって、API についての詳細はあまり多くないので、間違えないように注意してください。

基本的に、最も頻繁に使用するのは emalloc(size_t)efree(void *) です。

サイズ size の単一の nmemb を割り当て、領域をゼロに設定する ecalloc(size_t nmemb, size_t size) も提供されます。経験豊富な C プログラマであれば、可能な限り、ecalloc() ではなく ecalloc() を使用するのが最善であることを知っているはずです。メモリ領域をゼロに設定します。これは、ポインタ エラーの検出に非常に役立ちます。 emalloc() は基本的に libc malloc() と同じように機能することに注意してください。さまざまなプールで十分な大きさの領域を検索し、最適なスペースを提供します。したがって、ガベージ コレクションされたポインターが生成される可能性があります。 次に、

safe_emalloc(size_t nmemb, size_t size, size_t offset)

、つまり emalloc(size * nmemb offset) ですが、オーバーフローがチェックされます。この API 呼び出しは、提供する必要がある番号が信頼できないソース (ユーザーランドなど) からのものである場合に使用する必要があります。 文字列に関しては、

estrdup(char *)

および estrndup(char *, size_t len) を使用して、文字列またはバイナリ文字列をコピーできます。 何が起こっても、ZendMM から返されたポインタは、

libc の free()

ではなく、ZendMM の efree() を呼び出して解放する必要があります。

永続的な割り当てに関する説明。永続的な割り当ては、リクエスト間で有効なままです。通常、これを行うには通常の libc

malloc/free

を使用しますが、ZendMM には libc アロケーターのショートカット、つまり「永続的」API がいくつかあります。 API は "p" という文字で始まり、ZendMM 割り当てまたは永続的割り当てのいずれかを選択できます。したがって、pemalloc(size_t, 1) は単に malloc() であり、pefree(void *, 1)free(),# です。 ##pestrdup(void *, 1)strdup() です。ただ言って。 Zend Memory Manager Debug Shield

ZendMM は次の機能を提供します:

メモリ消費管理。

    メモリ リークの追跡と自動解放。
  • 既知のサイズのバッファを事前に割り当て、ホット キャッシュをアイドル状態に保つことで、割り当てを高速化します。
  • メモリ消費管理
ZendMM は、「メモリ制限」関数。 ZendMM レイヤーを使用して割り当てられたすべてのバイトがカウントされ、合計されます。 INI の

memory_limit

に達すると何が起こるかはご存知でしょう。これは、ZendMM 経由で実行された割り当てが PHP ユーザーランドの

memory_get_usage() に反映されることも意味します。 これは、PHP プロセスのヒープ サイズを追跡するのに役立つため、拡張機能の開発者としては良いことです。

メモリ制限エラーが発生した場合、エンジンは現在のコード位置からキャプチャ ブロックに解放され、正常に終了します。ただし、制限を超えたコードの場所に戻ることはできません。これに備えて準備をしておく必要があります。

理論的には、これは、ZendMM が NULL ポインタを返すことができないことを意味します。オペレーティング システムからの割り当てが失敗した場合、または割り当てによってメモリ制限エラーが発生した場合、コードは catch ブロックに突入し、割り当て呼び出しには戻りません。

何らかの理由でこの保護をバイパスする必要がある場合は、

malloc()

などの従来の libc 呼び出しを使用する必要があります。いずれにせよ、注意して自分が何をしているのかを知ってください。 ZendMM を使用する場合は、大量のメモリを割り当てる必要があり、PHP の

memory_limit を超える可能性があります。したがって、別のアロケータ (libc など) を使用しますが、拡張機能によって現在のプロセス ヒープ サイズが増加することに注意してください。 memory_get_usage() は PHP では見ることができませんが、OS の機能 (/proc/{pid}/maps など) を使用して現在のヒープを分析できます。 Note ZendMM を完全に無効にする必要がある場合は、

USE_ZEND_ALLOC = 0
環境変数を使用して PHP を起動できます。こうすることで、ZendMM API (emalloc() など) へのすべての呼び出しが libc 呼び出しに誘導され、ZendMM が無効になります。これは、メモリをデバッグする場合に特に便利です。

メモリ リーク トレースZendMM の主なルールを思い出してください。ZendMM はリクエストの開始時に開始され、リクエストを処理するために動的メモリが必要になったときに API を呼び出すことを期待します。現在のリクエストが終了すると、ZendMM はシャットダウンします。

閉じると、すべてのライブ ポインターが参照され、PHP のデバッグ ビルドを使用している場合は、メモリ リークについて警告が表示されます。

もう少しわかりやすく説明しましょう。現在のリクエストの最後に ZendMM がアクティブなメモリ ブロックを見つけた場合、それはこれらのメモリ ブロックがリークしていることを意味します。メモリを割り当てた人はメモリを解放しているはずなので、リクエストの終了時点で ZendMM ヒープ上にアクティブなメモリ ブロックが存在してはなりません。

如果您忘记释放块,它们将全部显示在 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():将会崩溃。

以上がPHP用のZendメモリマネージャーの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:learnku.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート