小编典典

为什么这个吃记忆的人真的不吃记忆?

linux

我想创建一个程序来模拟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而现在恰好是50 GB的RAM。它按1
MB分配内存,并精确打印无法分配更多内存的点,这样我就知道它设法吃了哪个最大值。

问题是它有效。即使在具有1 GB物理内存的系统上。

当我检查顶部时,我发现该进程占用了50 GB的虚拟内存,而占用的驻留内存不到1 MB。有没有办法创建确实消耗掉它的内存消耗者?

系统规范:Linux内核3.16(Debian)最有可能启用了过量提交(不确定如何检出),并且没有交换和虚拟化。


阅读 183

收藏
2020-06-07

共1个答案

小编典典

当您的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个字节)就足够了。那是因为从内核到进程的所有内存分配都是以内存页面粒度完成的,这又是因为硬件不允许以较小粒度进行分页。

2020-06-07