1. ホーム
  2. c

[解決済み] なぜ、このメモリイーターは本当にメモリを食べないのか?

2022-04-28 06:56:51

質問

Unixサーバでメモリ不足(OOM)状態をシミュレートするプログラムを作りたいのですが、どうすればいいですか?私はこの超簡単なメモリイーターを作りました。

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

unsigned long long memory_to_eat = 1024 * 50000;
size_t eaten_memory = 0;
void *memory = NULL;

int eat_kilobyte()
{
    memory = realloc(memory, (eaten_memory * 1024) + 1024);
    if (memory == NULL)
    {
        // realloc failed here - we probably can't allocate more memory for whatever reason
        return 1;
    }
    else
    {
        eaten_memory++;
        return 0;
    }
}

int main(int argc, char **argv)
{
    printf("I will try to eat %i kb of ram\n", memory_to_eat);
    int megabyte = 0;
    while (memory_to_eat > 0)
    {
        memory_to_eat--;
        if (eat_kilobyte())
        {
            printf("Failed to allocate more memory! Stucked at %i kb :(\n", eaten_memory);
            return 200;
        }
        if (megabyte++ >= 1024)
        {
            printf("Eaten 1 MB of ram\n");
            megabyte = 0;
        }
    }
    printf("Successfully eaten requested memory!\n");
    free(memory);
    return 0;
}

で定義されただけのメモリを消費します。 memory_to_eat であり、現在ではちょうど50GBのRAMとなっています。1MBずつメモリを割り当て、さらに割り当てに失敗した箇所を正確に表示するので、どの最大値を食べたのかがわかります。

問題は、それがうまくいくかどうかです。物理メモリが1GBのシステムでもです。

topを確認すると、このプロセスは50GBの仮想メモリを食べ、常駐メモリは1MB以下しか食べていないことがわかります。本当に消費するメモリイーターを作る方法はないでしょうか?

システムの仕様 Linuxカーネル3.16( Debian ) おそらくオーバーコミット有効で (確認方法は不明)、スワップ無し、仮想化されています。

解決方法は?

を使用する場合 malloc() 実装はシステムカーネルにメモリを要求します。 sbrk() または mmap() システムコール)、カーネルは、あなたがメモリを要求したことと、アドレス空間内のどこに配置されるかを記録するだけです。 そのページを実際にマッピングすることはまだありません。 .

その後、プロセスが新しい領域内のメモリにアクセスすると、ハードウェアはセグメンテーションフォールトを認識し、その状態をカーネルに警告します。カーネルは自分自身のデータ構造でそのページを調べ、そこにゼロページがあるはずであることを発見し、ゼロページをマッピングし(おそらく最初にページキャッシュからページを退避させる)、割り込みから戻ります。カーネルの操作は完全に透過的です(カーネルがその仕事をする間の短い遅延を除いて)。

この最適化により、システムコールは非常に迅速に戻ることができ、最も重要なことは、マッピングが行われる際にプロセスにコミットされるリソースを回避することができます。これにより、通常では決して必要としないような大きなバッファを、大量のメモリを消費することなく確保することができます。


ですから、メモリイーターをプログラムする場合は、割り当てたメモリで実際に何かをする必要があります。そのためには、コードに一行加えるだけでいいのです。

int eat_kilobyte()
{
    if (memory == NULL)
        memory = malloc(1024);
    else
        memory = realloc(memory, (eaten_memory * 1024) + 1024);
    if (memory == NULL)
    {
        return 1;
    }
    else
    {
        //Force the kernel to map the containing memory page.
        ((char*)memory)[1024*eaten_memory] = 42;

        eaten_memory++;
        return 0;
    }
}

なお、各ページ(X86では4096バイトを含む)内の1バイトに書き込めば完全に事足りる。これは、カーネルからプロセスへのすべてのメモリ割り当てがメモリページ粒度で行われるためで、ひいてはそれより小さい粒度でのページングを許さないハードウェアのためでもあります。