我想创建一个程序来模拟 Unix 服务器上的内存不足 (OOM) 情况。我创建了这个 super 简单的内存吞噬者:
#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) 很可能启用了过量使用(不确定如何检查),没有交换和虚拟化。
最佳答案
当您的 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 个字节)。这是因为从内核到进程的所有内存分配都是以内存页面粒度完成的,这反过来又是因为硬件不允许以更小的粒度进行分页。
https://stackoverflow.com/questions/33234139/