Linux のメモリ割り当て戦略を 1 つの記事で理解する

王林
リリース: 2024-02-12 11:57:02
転載
928 人が閲覧しました

Linux プロセスのメモリ配分はどのようになりますか?

Linux オペレーティング システムでは、仮想アドレス空間の内部は カーネル空間とユーザー空間に分割されており、桁が異なるシステムではアドレス空間の範囲も異なります。たとえば、最も一般的な 32 ビット システムと 64 ビット システムは次のとおりです。

一文读懂 Linux 内存分配策略

ここから見ることができます:

  • 32 ビット システムのカーネル空間は最上位の 1G を占め、残りの 3G はユーザー空間です。
  • 64 ビット システムのカーネル空間とユーザー空間は両方とも 128T で、それぞれメモリ空間全体の最高位と最低位の部分を占め、残りの中間部分は未定義です。

カーネル空間とユーザー空間の違いについて話しましょう:

  • プロセスがユーザー モードの場合、プロセスはユーザー空間メモリにのみアクセスできます;
  • カーネル状態に入った後にのみ、カーネル空間内のメモリにアクセスできます;

各プロセスには独自の独立した仮想メモリがありますが、各仮想メモリ内のカーネル アドレスは実際には同じ物理メモリに関連付けられています。このようにして、プロセスがカーネル状態に切り替わった後、カーネル空間メモリに簡単にアクセスできます。

一文读懂 Linux 内存分配策略

次に、仮想空間の分割について詳しく学びましょう。ユーザー空間とカーネル空間はさまざまな方法で分割されます。カーネル空間の配分については多くは語りません。

ユーザー空間の分布を見てみましょう。32 ビット システムを例として、それらの関係を示す図を描きました。

この図から、ユーザー空間メモリが

低位から高位まで 6 つの異なるメモリ セグメントに分割されていることがわかります。:

一文读懂 Linux 内存分配策略 プログラム ファイル セグメント (バイナリ実行可能コードを含む);
  • 初期化されたデータ セグメント (静的定数を含む);
  • 未初期化の静的変数を含む、初期化されていないデータ セグメント;
  • 動的に割り当てられたメモリを含むヒープ セグメントは、下位アドレスから始まり、上に向かって増加します。
  • ダイナミック ライブラリ、共有メモリなどを含むファイル マッピング セグメントは、低いアドレスから始まり、上に向かって増加します (ハードウェアとカーネルのバージョンに応じて);
  • ローカル変数や関数呼び出しコンテキストなどを含むスタック セグメント。スタック サイズは固定で、通常は 8 MB です。もちろん、システムはサイズをカスタマイズできるパラメーターも提供します;
  • これら 6 つのメモリ セグメントのうち、ヒープ セグメントとファイル マッピング セグメントのメモリは動的に割り当てられます。たとえば、C 標準ライブラリの malloc() または mmap() を使用すると、それぞれヒープ セグメントとファイル マッピング セグメントにメモリを動的に割り当てることができます。
malloc はどのようにメモリを割り当てますか?

実際には、malloc() はシステム コールではなく、メモリを動的に割り当てるための C ライブラリ内の関数です。 malloc メモリを申請する場合、オペレーティング システムからヒープ メモリを申請するには 2 つの方法があります。

方法 1: brk() システム コールを通じてヒープからメモリを割り当てる

    方法 2: mmap() システム コールを通じてファイル マッピング領域にメモリを割り当てます;
  • 方法 1 の実装は非常に簡単です。brk() 関数を使用して「ヒープの最上位」ポインタを上位アドレスに移動し、新しいメモリ空間を取得します。以下に示すように:
方法 2 では、mmap() システム コールで「プライベート匿名マッピング」メソッドを使用して、ファイル マッピング領域にメモリの一部を割り当てます。これは、ファイル マッピング領域からメモリの一部を「盗む」ことになります。 。以下に示すように:

一文读懂 Linux 内存分配策略

#「

malloc() はどのような状況で brk() を通じてメモリを割り当てますか? mmap() を通じてメモリが割り当てられるのはどのようなシナリオですか?

#malloc() には、ソース コードでデフォルトで定義されたしきい値があります:

ユーザーが割り当てたメモリが 128 KB 未満の場合は、brk();
  • を通じてメモリを申請します。 ユーザーが割り当てたメモリが 128 KB を超える場合は、mmap();
  • を通じてメモリを申請します。
  • glibc のバージョンが異なると、定義されるしきい値も異なることに注意してください。

malloc() は物理メモリを割り当てますか?

いいえ、

malloc() は仮想メモリを割り当てます

割り当てられた仮想メモリにアクセスしない場合、仮想メモリは物理メモリにマッピングされないため、物理メモリを占有しません。

割り当てられた仮想アドレス空間にアクセスする場合のみ、オペレーティング システムはページ テーブルを検索し、仮想メモリに対応するページが物理メモリにないことを検出し、ページ フォールト割り込みをトリガーし、オペレーティング システムは仮想メモリと物理メモリ、メモリ間のマッピング関係を確立します。

malloc(1) はどのくらいの仮想メモリを割り当てますか?

malloc() がメモリを割り当てるとき、ユーザーが期待するバイト数に応じてメモリ空間を割り当てるのではなく、

はより大きな空間をメモリ プール

として事前に割り当てます。 事前に割り当てられる特定のスペースの量は、malloc によって使用されるメモリ マネージャーに関連しています。分析には、malloc のデフォルトのメモリ マネージャー (Ptmalloc2) を使用します。

次に、次のコードを使用して実験を行い、malloc を通じて 1 バイトのメモリを申請したときにオペレーティング システムによって実際に割り当てられるメモリ領域の量を確認してみましょう。

リーリー

実行コード (

使用する glibc ライブラリのバージョンが 2.17

であることを事前に説明しておきます):

/proc//maps ファイルを通じてプロセスのメモリ分布を確認できます。この 1 バイトのメモリ開始アドレスによって、マップ ファイル内のメモリ アドレスの範囲をフィルタリングします。 一文读懂 Linux 内存分配策略 リーリー

この例で割り当てられたメモリは 128 KB 未満であるため、メモリは brk() システム コールを通じてヒープ領域に適用され、右端に [heap] マークが表示されます。

ご覧のとおり、ヒープ領域のメモリ アドレス範囲は 00d73000 ~ 00d94000 で、この範囲のサイズは 132KB です。これは、

malloc(1) が実際に 132K バイトのメモリを事前に割り当てることを意味します## #。

一部の学生は、プログラムで出力されたメモリの開始アドレスが d73010 であり、マップ ファイルではヒープ メモリ空間の開始アドレスが d73000 であることに気づいたかもしれません。なぜ余分な 0x10 (16 バイト) があるのでしょうか?この質問は今は脇に置いておいて、後で説明します。

#free はメモリを解放してオペレーティング システムに戻しますか?

free() 関数でメモリを解放した後、上記のプロセスを実行してヒープ メモリがまだあるかどうかを確認してみましょう。

下の図からわかるように、メモリを解放した後もヒープ メモリはまだ存在しており、オペレーティング システムに返されていません。

一文读懂 Linux 内存分配策略

これは、この 1 バイトをオペレーティング システムに解放するのではなく、キャッシュして malloc のメモリ プールに入れる方が良いためです。プロセスが 1 バイトのメモリを再度適用すると、この速度ははるかに高速です。

一文读懂 Linux 内存分配策略もちろん、プロセスが終了すると、オペレーティング システムはプロセスのすべてのリソースを再利用します。

上記の空きメモリは、brk() メソッドを介して malloc によって適用されたメモリ用であり、ヒープ メモリはまだ存在します。

malloc が mmap を通じてメモリに適用された場合、free がメモリを解放した後、メモリがオペレーティング システムに返されます。

malloc を通じて 128 KB のメモリを申請し、malloc が mmap を通じてメモリを割り当てることを検証する実験を行ってみましょう。

リーリー

実行コード:

一文读懂 Linux 内存分配策略

プロセスのメモリ分布を見ると、右端に[先頭]マークがなく、ファイルマッピング領域からmmap経由で匿名マッピングにより匿名メモリが割り当てられていることがわかります。

一文读懂 Linux 内存分配策略

それでは、このメモリを解放して見てみましょう:

一文读懂 Linux 内存分配策略

128 KB メモリの開始アドレスを再度確認すると、メモリがもう存在していないことがわかり、オペレーティング システムに返されたことがわかります。

一文读懂 Linux 内存分配策略

「malloc によって要求され、free によって解放されたメモリはオペレーティング システムに返されますか?」という質問については、次のように要約できます。

    malloc が
  • brk() を通じてメモリを適用し、free がメモリを解放すると、 はメモリをオペレーティング システムに返さず、次回使用されるまで malloc のメモリ プールにキャッシュします
  • free が
  • mmap() を通じて malloc によって割り当てられたメモリを解放すると、 はメモリをオペレーティング システムに返し、メモリは実際に解放されます

メモリ割り当てに mmap を使用しないのはなぜでしょうか?

OSからのメモリの申請にはシステムコールが必要なため、システムコールを実行するにはカーネル状態に入り、その後ユーザー状態に戻る必要があり、実行状態への切り替えに時間がかかります。

したがって、メモリを確保する操作は頻繁にシステム コールを行わないようにする必要があり、mmap を使用してメモリを確保すると、毎回システム コールを実行する必要があります。

また、mmap によって割り当てられたメモリは解放されるたびにオペレーティング システムに返されるため、mmap によって割り当てられた仮想アドレスは毎回ページ フォールト状態になり、仮想アドレスにアクセスするたびにページ フォルト状態になります。初めて、ページフォルト割り込みがトリガーされます。

つまり、

mmapで確保したメモリを頻繁に使用すると、その都度実行状態が切り替わるだけでなく、(仮想アドレスへの最初のアクセス後)ページフォルト割り込みも発生します。これにより CPU が消費されます。 が大きいほど。

これら 2 つの問題を改善するために、malloc が brk() システム コールを通じてヒープ領域のメモリに適用するとき、ヒープ領域は連続しているため、より大きなメモリをメモリ プールとして直接事前に割り当てます。解放されると、メモリ プールにキャッシュされます。

次回メモリを申請する際には、該当するメモリブロックをメモリプールから直接取り出すだけで、このメモリブロックの仮想アドレスと物理アドレスのマッピング関係が残っている可能性があります。システムの削減 呼び出しの数により、ページ フォールト割り込みの数も削減され、CPU 消費量が大幅に削減されます。

brk は非常に素晴らしいので、すべての割り当てに brk を使用してみてはいかがでしょうか?

brk を通じてヒープ領域から割り当てられたメモリはオペレーティング システムに返されないことを前述したため、そのようなシナリオを考えてみましょう。

10k、20k、30k の 3 つのメモリを連続して申請した場合、10k と 20k が解放されると空きメモリ領域になります。次回申請されるメモリが 30k 未満であれば、この空きメモリは使用できます。再利用できるスペース。

しかし、次回要求されるメモリが 30k を超える場合は、空きメモリ容量がなくなり、OS に申請する必要があり、実際に使用されるメモリは増加し続けます。 一文读懂 Linux 内存分配策略

したがって、システムが特に小さなメモリ ブロックに対して頻繁にマロックと解放を行うと、ヒープ内に使用​​できないフラグメントがどんどん生成され、「メモリ リーク」が発生します。この「漏れ」現象は、valgrind では検出できません。

したがって、malloc の実装では、brk と mmap の動作の違い、利点、欠点が十分に考慮され、mmap がメモリ空間を割り当てる前に、デフォルトで大きなメモリ ブロック (128KB) が割り当てられます。

free() 関数はメモリ アドレスのみを渡しますが、解放すべきメモリの量を知ることができるのはなぜでしょうか?

malloc によってユーザー モードに返されるメモリの開始アドレスは、プロセスのヒープ領域の開始アドレスより 16 バイト大きいと前に述べたことを覚えていますか?

追加の 16 バイトには、メモリ ブロックのサイズなど、メモリ ブロックの説明情報が格納されます。

一文读懂 Linux 内存分配策略

このようにして、free() 関数が実行されると、free は受信したメモリ アドレスを 16 バイト左にオフセットし、この 16 バイトから現在のメモリ ブロック サイズを分析します。どれだけのメモリを解放するか。

以上がLinux のメモリ割り当て戦略を 1 つの記事で理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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