web-dev-qa-db-ja.com

Linuxは「RAMを使い果たす」ことができますか?

ホスティングされたVPSがRAMを使いすぎたためにプロセスが予期せず終了することについて不平を言っている人々のウェブの周りにいくつかの投稿を見ました。

これはどのようにして可能ですか?現代のすべてのOSは、物理RAMを超えるものに対してディスクスワップを使用するだけで「無限RAM」を提供すると思いました。これは正しいです?

プロセスが「RAMが少ないために殺された」場合、何が起こる可能性がありますか?

21
themirror

プロセスが「RAMが少ないために殺された」場合、何が起こる可能性がありますか?

デフォルトでは、Linuxはアプリケーションコードからのメモリの追加要求を決して拒否しないと言われています-たとえば、 malloc()1 これは実際には真実ではありません。デフォルトではヒューリスティックを使用します。

アドレス空間の明らかなオーバーコミットは拒否されます。一般的なシステムに使用されます。オーバーコミットを許可してスワップの使用量を減らす一方で、深刻なワイルドアロケーションが失敗することを保証します。

[linux_src]/Documentation/vm/overcommit-accountingから(引用符はすべて3.11ツリーから)。 「真剣にワイルドな割り当て」として数えられるものが正確に明示されていないため、詳細を決定するためにソースを調べる必要があります。脚注2(下記)の実験的方法を使用して、ヒューリスティックを反映させてみることもできます-これに基づいて、私の最初の経験的観察は、理想的な状況(==システムがアイドル状態)の場合、スワップがある場合は、RAMの約半分を割り当てることができます。スワップがある場合は、RAMに加えてすべてのスワップが追加されます。それ以上です。以下プロセスごと(ただし、この制限に注意してくださいis動的であり、状態により変更される可能性があります。脚注5のいくつかの観察を参照してください)。

RAMプラススワップの半分は、明示的に/proc/meminfoの "CommitLimit"フィールドのデフォルトです。これが意味するところです-実際に、([src]/Documentation/filesystems/proc.txtから)説明した制限とは関係がないことに注意してください。 :

CommitLimit:オーバーコミット率( 'vm.overcommit_ratio')に基づいて、これは現在割り当て可能なメモリの総量ですonシステム。この制限は、厳密なオーバーコミットアカウンティングが有効になっている場合にのみ遵守されます(「vm.overcommit_memory」のモード2)。 CommitLimitは、次の式で計算されます。CommitLimit =( 'vm.overcommit_ratio' *物理RAM)+スワップたとえば、1Gの物理RAMおよび7Gの ' vm.overcommit_ratio 'が30の場合、CommitLimitは7.3Gになります。

以前に引用したオーバーコミットアカウンティングのドキュメントでは、デフォルトのvm.overcommit_ratioは50であると記載されています。したがって、sysctl vm.overcommit_memory=2を使用する場合は、vm.covercommit_ratio(sysctlを使用)を調整して結果を確認できます。 デフォルトのモードは、CommitLimitが強制されておらず、 "アドレス空間の明らかなオーバーコミットが拒否される"場合は、vm.overcommit_memory=0です。

デフォルトの戦略にはプロセスごとのヒューリスティックな制限があり、「深刻なワイルドな割り当て」を防止しますが、システム全体として、深刻なワイルドな割り当てを自由に行うことができます。4 これは、ある時点でメモリが不足し、OOM killerを介して一部のプロセスに破産を宣言する必要があることを意味します。

OOMキラーは何を殺しますか?メモリがない場合にメモリを要求したプロセスであるとは限りません。それは必ずしも本当に有罪なプロセスではないためです。さらに重要なのは、システムが存在する問題からシステムを最も迅速に取り除くプロセスであるとは限りません。

これは here から引用されており、おそらく2.6.xソースを引用しています。

/*
 * oom_badness - calculate a numeric value for how bad this task has been
 *
 * The formula used is relatively simple and documented inline in the
 * function. The main rationale is that we want to select a good task
 * to kill when we run out of memory.
 *
 * Good in this context means that:
 * 1) we lose the minimum amount of work done
 * 2) we recover a large amount of memory
 * 3) we don't kill anything innocent of eating tons of memory
 * 4) we want to kill the minimum amount of processes (one)
 * 5) we try to kill the process the user expects us to kill, this
 *    algorithm has been meticulously tuned to meet the principle
 *    of least surprise ... (be careful when you change it)
 */

それはまともな根拠のようです。ただし、フォレンジックを行わなければ、#5(#1の冗長)は実装が難しいように思われ、#3は#2の冗長です。したがって、これを#2/3と#4に削減することを検討することは理にかなっています。

私は最近の情報源(3.11)を手探りで調べ、その間にこのコメントが変更されていることに気付きました:

/**
 * oom_badness - heuristic function to determine which candidate task to kill
 *
 * The heuristic for determining which task to kill is made to be as simple and
 * predictable as possible.  The goal is to return the highest value for the
 * task consuming the most memory to avoid subsequent oom failures.
 */

これは、#2についてもう少し明確です: "目標は、後続のoomエラーを回避するために、最も多くのメモリを消費するタスクを[kill]することです。"および含意#4("プロセスの最小量を強制終了したい(one))

OOMキラーの動作を確認したい場合は、脚注5を参照してください。


1 妄想のジルはありがたいことに私を取り除きました、コメントを参照してください。


2 次のCの簡単なビットは、メモリのチャンクをますます大きくして、追加のリクエストがいつ失敗するかを判断するように要求します。

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#define MB 1 << 20

int main (void) {
    uint64_t bytes = MB;
    void *p = malloc(bytes);
    while (p) {
        fprintf (stderr,
            "%lu kB allocated.\n",
            bytes / 1024
        );
        free(p);
        bytes += MB;
        p = malloc(bytes);
    }
    fprintf (stderr,
        "Failed at %lu kB.\n",
        bytes / 1024
    );
    return 0;
}            

Cがわからない場合は、このgcc virtlimitcheck.c -o virtlimitcheckをコンパイルしてから./virtlimitcheckを実行できます。プロセスは要求する領域を一切使用しないため、完全に無害です。つまり、実際にRAMを使用することはありません。

4 GBシステムと6 GBのスワップを備えた3.11 x86_64システムでは、約7400000 kBで失敗しました。数は変動するので、おそらく状態が要因です。これは偶然にも、/proc/meminfoCommitLimitに近いですが、vm.overcommit_ratioを使用してこれを変更しても、違いはありません。 3.6.11 32ビットARM 448 MBのシステムで64 MBのスワップを使用しますが、230 MBで失敗します。これは興味深いことです。 RAMの容量、2番目の場合は約1/4、つまりスワップの量が重要であることを意味します。これは、障害しきい値が約1.95 GBに下がったときに、最初のシステムでスワップをオフにすることで確認されました。小さなARMボックスと非常に似た比率です。

しかし、これは本当にプロセスごとですか?のようです。以下の短いプログラムは、ユーザー定義のメモリのチャンクを要求し、それが成功した場合は、returnがヒットするのを待ちます。これにより、複数の同時インスタンスを試すことができます。

#include <stdio.h>
#include <stdlib.h>

#define MB 1 << 20

int main (int argc, const char *argv[]) {
    unsigned long int megabytes = strtoul(argv[1], NULL, 10);
    void *p = malloc(megabytes * MB);
    fprintf(stderr,"Allocating %lu MB...", megabytes);
    if (!p) fprintf(stderr,"fail.");
    else {
        fprintf(stderr,"success.");
        getchar();
        free(p);
    }
    return 0;
}

ただし、RAMの量に厳密に関係なく、使用に関係なくスワップすることに注意してください。システム状態の影響に関する観察については、脚注5を参照してください。


CommitLimitは、vm.overcommit_memory = 2の場合に許可されるアドレススペースの量を示しますthe systemおそらく、割り当て可能な量は、すでにコミットされている量、つまりCommitted_ASフィールドから差し引かれた量になるはずです。

これを実証する潜在的に興味深い実験は、#include <unistd.h>をvirtlimitcheck.c(脚注2を参照)の先頭に追加し、fork()ループの直前にwhile()を追加することです。面倒な同期をせずに、ここで説明するように動作することは保証されていませんが、YMMVで動作する可能性は十分あります。

> sysctl vm.overcommit_memory=2
vm.overcommit_memory = 2
> cat /proc/meminfo | grep Commit
CommitLimit:     9231660 kB
Committed_AS:    3141440 kB
> ./virtlimitcheck 2&> tmp.txt
> cat tmp.txt | grep Failed
Failed at 3051520 kB.
Failed at 6099968 kB.

これは理にかなっています-tmp.txtを詳細に見ると、1つが明らかに他の1つが失敗するのに十分であると主張するまで、プロセスがより大きな割り当てを交互に繰り返すことがわかります(これはpidを出力に投入する方が簡単です)。勝者は、CommitLimitからCommitted_ASを差し引いたものまですべてを自由に取得できます。


4 この時点で、仮想アドレス指定と要求ページングをまだ理解していない場合、そもそも価値があるのは、カーネルがユーザーランドプロセスに割り当てるものは物理メモリではなく、それが物理メモリではないということです。 仮想アドレススペース。たとえば、プロセスが何かのために10 MBを予約する場合、それは(仮想)アドレスのシーケンスとしてレイアウトされますが、それらのアドレスはまだ物理メモリに対応していません。このようなアドレスにアクセスすると、ページフォールトが発生し、カーネルはそれを実際のメモリにマップして、実際の値を格納できるようにします。プロセスは通常、実際にアクセスするよりもはるかに多くの仮想空間を予約するため、カーネルはRAMを最も効率的に使用できます。ただし、物理メモリは依然として有限のリソースであり、そのすべてが仮想アドレス空間にマップされている場合、一部の仮想アドレス空間を削除して、RAMを解放する必要があります。


5 最初に警告vm.overcommit_memory=0でこれを試す場合は、システムをフリーズするため、最初に作業を保存し、重要なアプリケーションをすべて閉じてください〜 90秒でプロセスが終了します!

アイデアは、90秒後にタイムアウトする fork bomb を実行することです。フォークはスペースを割り当て、その一部はRAMに大量のデータを書き込み、その間stderrにレポートします。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>

/* 90 second "Verbose hungry fork bomb".
Verbose -> It jabbers.
Hungry -> It grabs address space, and it tries to eat memory.

BEWARE: ON A SYSTEM WITH 'vm.overcommit_memory=0', THIS WILL FREEZE EVERYTHING
FOR THE DURATION AND CAUSE THE OOM KILLER TO BE INVOKED.  CLOSE THINGS YOU CARE
ABOUT BEFORE RUNNING THIS. */

#define STEP 1 << 30 // 1 GB
#define DURATION 90

time_t now () {
    struct timeval t;
    if (gettimeofday(&t, NULL) == -1) {
        fprintf(stderr,"gettimeofday() fail: %s\n", strerror(errno));
        return 0;
    }
    return t.tv_sec;
}

int main (void) {
    int forks = 0;
    int i;
    unsigned char *p;
    pid_t pid, self;
    time_t check;
    const time_t start = now();
    if (!start) return 1;

    while (1) {
    // Get our pid and check the elapsed time.
        self = getpid();
        check = now();
        if (!check || check - start > DURATION) return 0;
        fprintf(stderr,"%d says %d forks\n", self, forks++);
    // Fork; the child should get its correct pid.
        pid = fork();
        if (!pid) self = getpid();
    // Allocate a big chunk of space.
        p = malloc(STEP);
        if (!p) {
            fprintf(stderr, "%d Allocation failed!\n", self);
            return 0;
        }
        fprintf(stderr,"%d Allocation succeeded.\n", self);
    // The child will attempt to use the allocated space.  Using only
    // the child allows the fork bomb to proceed properly.
        if (!pid) {
            for (i = 0; i < STEP; i++) p[i] = i % 256;
            fprintf(stderr,"%d WROTE 1 GB\n", self);
        }
    }
}                        

これをgcc forkbomb.c -o forkbombでコンパイルします。まず、sysctl vm.overcommit_memory=2で試してください。おそらく次のようになります。

6520 says 0 forks
6520 Allocation succeeded.
6520 says 1 forks
6520 Allocation succeeded.
6520 says 2 forks
6521 Allocation succeeded.
6520 Allocation succeeded.
6520 says 3 forks
6520 Allocation failed!
6522 Allocation succeeded.

この環境では、この種のフォーク爆弾はそれほど遠くに行きません。 「Nフォークと言う」の数はプロセスの合計数ではなく、そのプロセスに至るまでのチェーン/ブランチ内のプロセスの数であることに注意してください。

ここで、vm.overcommit_memory=0で試してください。 stderrをファイルにリダイレクトすると、後で大まかな分析を行うことができます。例:

> cat tmp.txt | grep failed
4641 Allocation failed!
4646 Allocation failed!
4642 Allocation failed!
4647 Allocation failed!
4649 Allocation failed!
4644 Allocation failed!
4643 Allocation failed!
4648 Allocation failed!
4669 Allocation failed!
4696 Allocation failed!
4695 Allocation failed!
4716 Allocation failed!
4721 Allocation failed!

15個のプロセスのみが1 GBの割り当てに失敗しました-overcommit_memoryのヒューリスティック= 0 is状態の影響を受けることを示しています。プロセスはいくつありましたか? tmp.txtの終わりを見ると、おそらく100,000以上です。では、実際にはどのようにして1 GBを使用するのでしょうか。

> cat tmp.txt | grep WROTE
4646 WROTE 1 GB
4648 WROTE 1 GB
4671 WROTE 1 GB
4687 WROTE 1 GB
4694 WROTE 1 GB
4696 WROTE 1 GB
4716 WROTE 1 GB
4721 WROTE 1 GB

8-これもまた理にかなっています。当時、私は約3 GB RAM無料で6 GBのスワップがあったためです。

これを実行したら、システムログを確認してください。 OOMキラーレポートスコアが表示されます。おそらくこれはoom_badnessに関連しています。

42
goldilocks

1Gのデータをメモリにロードするだけの場合、これは起こりません。はるかに多くをロードするとどうなりますか?たとえば、Rにロードする必要がある数百万の確率を含む巨大なファイルを扱うことがよくあります。これには約16GBのRAMが必要です。

上記のプロセスを私のラップトップで実行すると、8GBのRAM=がいっぱいになるとすぐに、狂ったようにスワッピングが開始されます。ディスクからの読み取りが多いため、すべてが遅くなります。 RAMからの読み取りよりも遅い。2GBのRAMと10GBの空き容量しかないラップトップがある場合はどうなりますか?プロセスがすべてのRAMを取得すると、ディスクもいっぱいになります。スワップへの書き込みであり、RAMとスワップするスペースがなくなりました(人々は、まさにその理由で、スワップファイルではなく専用パーティションにスワップを制限する傾向があります)。 OOMキラーが入り、プロセスの強制終了を開始します。

そのため、システムは実際にメモリ不足になる可能性があります。さらに、スワッピングによるI/O操作が遅いために、大量のスワッピングシステムは、これが発生するずっと前に使用できなくなる可能性があります。一般的に、スワッピングをできるだけ回避したいと考えています。高速SSDを搭載したハイエンドサーバーでも、パフォーマンスが明らかに低下します。クラシックな7200RPMドライブを搭載した私のラップトップでは、大幅なスワップを行うと、システムが使用できなくなります。スワップが多ければ多いほど遅くなります。問題のプロセスをすぐに強制終了しないと、OOMキラーが介入するまですべてがハングします。

16
terdon

RAMがなくなってもプロセスは強制終了されず、次のようにだまされたときに強制終了されます。

  • Linuxカーネルは通常、プロセスが実際に利用可能な仮想メモリよりも大きな量の仮想メモリを割り当てる(つまり、予約する)ことを許可します(RAM +すべてのスワップ領域)
  • プロセスが予約したページのサブセットにのみアクセスする限り、すべてが正常に実行されます。
  • しばらくして、プロセスが所有するページにアクセスしようとしたが、空きページがなくなった場合、メモリ不足の状況が発生します。
  • OOMキラーは、必ずしも新しいページを要求したプロセスではなく、プロセスの1つを選択し、それを強制終了して仮想メモリを回復します。

これは、たとえばスワップ領域がスリープ状態のデーモンのメモリページでいっぱいになっている場合など、システムがアクティブにスワップしていないときでも発生する可能性があります。

これは、メモリをオーバーコミットしないOSでは発生しません。それらを使用すると、ランダムプロセスが強制終了されることはありませんが、仮想メモリが使い果たされている間に仮想メモリを要求する最初のプロセスでは、malloc(または同様の)がエラーで返されます。したがって、状況を適切に処理する機会が与えられます。ただし、これらのOSでは、RAMがまだ残っているときにシステムが仮想メモリを使い果たすこともあります。これは、非常にわかりにくく、一般に誤解されています。

5
jlliagre

利用可能なRAM=が使い果たされると、カーネルは処理のビットをディスクにスワップアウトし始めます。実際には、カーネルはRAMがほぼ使い果たされると、アイドル状態になるとプロアクティブにスワッピングを開始するため、アプリケーションが突然より多くのメモリを必要とする場合の応答性が向上します。

RAMは、プロセスのメモリを格納するためだけに使用されるわけではありません。通常の正常なシステムでは、RAMがプロセスによって使用されるのは、残りの半分はディスクキャッシュとバッファに使用されるため、実行中のプロセスとファイルの入出力のバランスが取れています。

スワップ空間は無限ではありません。ある時点で、プロセスがより多くのメモリを割り当て続けると、RAM=からのスピルオーバーデータがスワップを満たします。その場合、より多くのメモリを要求しようとするプロセスは、その要求が拒否されたことを確認します。

デフォルトでは、Linuxはメモリをオーバーコミットします。つまり、プロセスは、予約されているが使用されていないメモリで実行できる場合があります。オーバーコミットメントの主な理由は forking の動作です。プロセスがサブプロセスを起動すると、子プロセスは概念的には親のメモリのレプリカで動作します。2つのプロセスには最初、同じコンテンツのメモリがありますが、プロセスがそれぞれのスペースで変更を加えると、そのコンテンツは分岐します。これを完全に実装するには、カーネルは親のすべてのメモリをコピーする必要があります。これによりフォークが遅くなるため、カーネルは copy-on-write を実践します。最初に、子はすべてのメモリを親と共有します。いずれかのプロセスが共有ページに書き込むたびに、カーネルはそのページのコピーを作成して共有を解除します。

多くの場合、子供は多くのページをそのままにしておきます。カーネルが、各フォークで親のメモリ空間を複製するのに十分なメモリを割り当てた場合、子プロセスが使用しない予約で大量のメモリが無駄になります。したがって、オーバーコミット:カーネルが必要とするページ数の 推定 に基づいて、カーネルはそのメモリの一部のみを予約します。

プロセスがメモリを割り当てようとして、十分なメモリが残っていない場合、プロセスはエラー応答を受け取り、適切と判断して処理します。プロセスが非共有にする必要がある共有ページに書き込むことによって間接的にメモリを要求する場合、それは別の話です。この状況をアプリケーションに報告する方法はありません。そこには書き込み可能なデータがあり、それを読み取ることさえできると考えています。書き込みは、内部で少し複雑な操作を伴うだけです。カーネルが新しいメモリページを提供できない場合、カーネルが実行できることは、要求しているプロセスを強制終了するか、他のプロセスを強制終了してメモリをいっぱいにすることです。

この時点で、要求プロセスを強制終了することが明白な解決策であると考えるかもしれません。しかし、実際には、これはあまり良くありません。プロセスは、ページの1つにアクセスするだけで発生する重要なプロセスである可能性がありますが、それほど重要ではないプロセスが実行されている可能性があります。そのため、カーネルには、殺すプロセスを選択するための複雑なヒューリスティックが含まれています—(有名な) OOMキラー

他の回答から別の視点を追加するために、多くのVPSは、任意のサーバー上で複数の仮想マシンをホストします。単一のVMには、独自に使用するために指定された量のRAMがあります。多くのプロバイダーは「バーストRAM」を提供しており、指定された量を超えてRAMを使用できます。これは、短期的な使用のみを目的としています。この時間を超えた場合、ホストがプロセスを強制終了して、使用中のRAMの量を減らすことでペナルティを科される可能性があります。ホストマシンが過負荷になることで他の人が苦しむことはありません。

2
agweber