Linuxに「RAMが不足していますか?」

Linuxに「RAMが不足していますか?」

人々がホスティングされたVPSがあまりにも多くのRAMを使用してプロセスを予期せず終了することについて明らかに文句を言ういくつかの記事をオンラインで見ました。

どうやってこれができますか?すべての最新のオペレーティングシステムは、物理RAMのすべてのエントリに対してディスクスワップを使用して「無制限のRAM」を提供すると考えています。そうですか?

「RAM不足でプロセスが終了する」としたらどうなりますか?

ベストアンサー1

「RAM不足でプロセスが終了する」としたらどうなりますか?

Linuxは、基本的にアプリケーションコードから追加のメモリ要求を拒否しないと言いますmalloc()。例えば。1 実際、これは事実ではありません。経験的な方法は基本的に使用されます。

明らかなアドレス空間超過割り当ては拒否される。一般的なシステムの場合。スワップ使用量を減らすために過剰なコミットを可能にしながら、重大な割り当て失敗を保証します。

ソース[linux_src]/Documentation/vm/overcommit-accounting(すべての参照は3.11ツリーに由来)。正確に何が「真剣にクレイジーな割り当て」なのかは明確ではないので、詳細を確認するにはソースコードを詳しく調べる必要があります。また、脚注2(下)の実験的アプローチを使用して、ヒューリスティックについていくつかの洞察を試すこともできます。これに基づいて、私の初期の経験的観察は、理想的な状況(==システムアイドル)で行わなければ次のようになります。スワップがない場合はRAMの約半分を割り当てることができ、スワップがある場合はRAMの半分にすべてのスワップを加えて割り当てます。つまり、ややプロセスごと(ただし、この制限事項にご注意ください。はい動的であり、状態によって変わることがあります。脚注5のいくつかの観察を参照してください。

RAMの半分とスワップのデフォルト値は明示的です/proc/meminfo。これが意味するところです。実際、今説明した制限とは何の関係もありません[src]/Documentation/filesystems/proc.txt

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

前述の超過コミット会計文書には、デフォルト値がvm.overcommit_ratio50と指定されています。したがって、をsysctl vm.overcommit_memory=2使用してvm.covercommit_ratioを調整しsysctl、結果を確認できます。3基本 モードは、強制CommitLimitされず、「明白なアドレス空間のオーバーコミットを拒否する」場合ですvm.overcommit_memory=0

基本ポリシーには、「重大なクレイジー割り当て」を防ぐためにプロセスごとの経験的制限がありますが、深刻にクレイジーな割り当てのためにシステム全体を無料で残します。4 これは、ある時点でメモリが不足する可能性があり、それを通過する必要があることを意味します。OOMキラー

OOMキラーは何を殺すのですか?メモリがない場合にメモリを要求したプロセスである必要はありません。これは必ずしも過失のあるプロセスではなく、より重要なのは、システムから現在の問題を最もよく除去するプロセスではない可能性があるためです。

これは以下で引用された。ここ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エラーを防ぐために、最も多くのメモリを消費するタスクを[終了]することです。」そしてヒント#4(「私たちは最小限のプロセス数を終了したいと思います(一つ))

OOMキラーが実際に何であるかを知りたい場合は、脚注5を参照してください。


1幸いにも、 Gilesは私の妄想を取り除きました。コメントをご覧ください。


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 システムでは、~7400000kB で失敗し、数字が変動するため状態が要因となることがあります。これはCommitLimitinに近いですが、/proc/meminfoこれで修正してもvm.overcommit_ratio違いはありません。ただし、64MBのスワップスペースを備えた3.6.11 32ビットARM 448MBシステムでは、約230MBで失敗しました。最初のケースではその量はRAMの量のほぼ2倍ですが、2番目の場合はRAMの約4分の1であるため、これは興味深いものです。つまり、スワップ量が重要な要因であることを強く示唆しています。最初のシステムでスワップ領域を閉じると、エラーしきい値がコンパクトなARMボックスと非常によく似た割合で約1.95 GBに低下したことを確認しました。

しかし、これは実際にすべてのプロセスに存在しますか?そんなようです。以下のショートプログラムはユーザーにメモリブロックを定義するように要求し、成功するとEnterキーが押されるのを待ちます。これにより、複数の同時インスタンスを試すことができます。

#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を参照してください。


3 は CommitLimit許可されるアドレス空間の量を示します。システムvm.overcommit_memory = 2の場合。おそらく割り当てることができるメモリ量は、明らかにフィールドであるコミットされたメモリ量を減らす必要がありますCommitted_AS

#include <unistd.h>これを実証するための潜在的に興味深い実験は、virtlimitcheck.c(脚注2を参照)の一番上に追加し、ループのfork()前に1つを追加することです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つのプロセスが明らかに十分なリソースを必要とするまで、プロセスがますます大きな割り当てに置き換えられていることがわかります(出力にpidを入れると簡単です)。リソースが多すぎると、他のプロセスが発生します。失敗。勝者はCommitLimit負数まですべてを自由に取ることができますCommitted_AS


4仮想アドレッシングとリクエストのページングがわからない場合、まず過剰なコミットを引き起こす原因は、カーネルがユーザ空間プロセスに割り当てるのが物理メモリではなく物理メモリであることです。 。仮想アドレス空間。たとえば、プロセスが何かのために10MBを予約すると、一連の(仮想)アドレスとして配置されますが、これらのアドレスはまだ物理メモリと一致しません。そのアドレスにアクセスすると、次の結果が得られます。ページエラーその後、カーネルは実際の値を保存できるように、それを物理メモリにマップしようとします。プロセスは多くの場合、実際にアクセスするよりも多くの仮想スペースを予約するため、カーネルはRAMを最も効率的に使用できます。ただし、物理メモリはまだ限られたリソースであるため、すべての物理メモリが仮想アドレス空間にマッピングされている場合は、一部の仮想アドレス空間を削除して一部のRAMを確保する必要があります。


最初の5つ警告:これを使用している場合は、vm.overcommit_memory=0システムが約90秒間停止し、一部のプロセスが終了するため、まず作業を保存して重要なアプリケーションをすべて閉じてください。

アイデアはフォーク爆弾90秒後にタイムアウトすると、フォークはスペースを割り当て、その一部はstderrに報告しながらRAMに大量のデータを書き込みます。

#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個のプロセスだけが1GBの割り当てに失敗しました。経験的に overcommit_memory = 0 を示します。はい国の影響を受けます。プロセスは何人ですか? tmp.txtの終わりを見ると、おそらく100,000より大きいでしょう。さて、実際に1GBをどのように使用できますか?

> 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 - 当時利用可能なRAMが約3GBで、スワップスペースが6GBだったので、もう一度意味があります。

これを実行したら、システムログを確認してください。 OOMキラーを見てスコアを見ることができます(おそらくこれはoom_badness

おすすめ記事