当前位置: 首页 > 知识库问答 >
问题:

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

贺子昂
2023-03-14

我想创建一个程序来模拟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

问题是它是有效的。即使在具有1的系统上

当我检查top时,我看到这个过程消耗了50

系统规范:Linux内核3.16(Debian)很可能启用了Overmit(不知道如何检查),没有交换和虚拟化。

共有3个答案

公冶浩慨
2023-03-14

这里正在进行合理的优化。在您使用内存之前,运行时实际上不会获取内存。

一个简单的memcpy就足以避免这种优化。(您可能会发现,在使用点之前,calloc仍然优化了内存分配。)

羊舌阎宝
2023-03-14

所有虚拟页面开始时都映射到同一个归零的物理页面。要使用物理页面,您可以通过向每个虚拟页面写入一些内容来弄脏它们。

如果以root身份运行,则可以使用mlock(2)或mlockall(2)让内核在分配页面时连接页面,而不必弄脏页面。(正常非根用户的ulimit-l只有64kiB。)

正如许多其他人所建议的那样,Linux内核似乎并不真正分配内存,除非您对其进行写入

这也修复了printf格式字符串与memory_to_eat和eaten_memory类型不匹配的问题,使用%zi打印size_t整数。要占用的内存大小(以kiB为单位)可以选择指定为命令行参数。

使用全局变量的凌乱设计并以1k而不是4k的页面增长,没有改变。

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

size_t memory_to_eat = 1024 * 50000;
size_t eaten_memory = 0;
char *memory = NULL;

void write_kilobyte(char *pointer, size_t offset)
{
    int size = 0;
    while (size < 1024)
    {   // writing one byte per page is enough, this is overkill
        pointer[offset + (size_t) size++] = 1;
    }
}

int eat_kilobyte()
{
    if (memory == NULL)
    {
        memory = malloc(1024);
    } else
    {
        memory = realloc(memory, (eaten_memory * 1024) + 1024);
    }
    if (memory == NULL)
    {
        return 1;
    }
    else
    {
        write_kilobyte(memory, eaten_memory * 1024);
        eaten_memory++;
        return 0;
    }
}

int main(int argc, char **argv)
{
    if (argc >= 2)
        memory_to_eat = atoll(argv[1]);

    printf("I will try to eat %zi kb of ram\n", memory_to_eat);
    int megabyte = 0;
    int megabytes = 0;
    while (memory_to_eat-- > 0)
    {
        if (eat_kilobyte())
        {
            printf("Failed to allocate more memory at %zi kb :(\n", eaten_memory);
            return 200;
        }
        if (megabyte++ >= 1024)
        {
            megabytes++;
            printf("Eaten %i  MB of ram\n", megabytes);
            megabyte = 0;
        }
    }
    printf("Successfully eaten requested memory!\n");
    free(memory);
    return 0;
}
慕麒
2023-03-14

当您的malloc()实现(通过sbrk()或mmap()系统调用)从系统内核请求内存时,内核只会注意到您已经请求了内存以及它将放置在您的地址空间中的位置。它实际上还没有映射这些页面。

当进程随后访问新区域内的内存时,硬件识别出分段错误并提醒内核注意该情况。然后内核在自己的数据结构中查找页面,发现您应该在那里有一个零页面,因此它映射到一个零页面(可能首先从页面缓存中驱逐一个页面)并从中断中返回。您的进程没有意识到发生了这些事情,内核操作是完全透明的(除了内核工作时的短暂延迟)。

这种优化允许系统调用快速返回,最重要的是,它避免了在映射时将任何资源投入到您的流程中。这允许进程保留在正常情况下不需要的相当大的缓冲区,而不用担心占用太多内存。

因此,如果你想编程一个内存食客,你绝对必须对你分配的内存做些什么。为此,您只需要在代码中html" target="_blank">添加一行:

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

 类似资料:
  • 问题内容: 我想创建一个程序来模拟Unix服务器上的内存不足(OOM)情况。我创建了这个超级简单的内存消耗者: 它消耗的内存与定义的内存一样多,而现在恰好是50 GB的RAM。它按1 MB分配内存,并精确打印无法分配更多内存的点,这样我就知道它设法吃了哪个最大值。 问题是它有效。即使在具有1 GB物理内存的系统上。 当我检查顶部时,我发现该进程占用了50 GB的虚拟内存,而占用的驻留内存不到1 M

  • 首先大家要知道,记忆化搜索是动态规划的入门。 什么是记忆化搜索?搜索的低效在于没有能够很好地处理重叠子问题;动态规划虽然比较好地处理了重叠子问题,但是在有些拓扑关系比较复杂的题目面前,又显得无奈。记忆化搜索正是在这样的情况下产生的,它采用搜索的形式和动态规划中递推的思想将这两种方法有机地综合在一起,扬长避短,简单实用,在信息学中有着重要的作用。 用一个公式简单地说:记忆化搜索=搜索的形式+动态规划

  • 不幸的是,这是TLES。由于我使用递归调用,所以我并不真的认为我遇到了重叠的子问题。 有办法让我记住我的解决方案吗?如果没有,为什么?

  • 我刚刚读了一些关于G1算法的博客。 我对记忆集的用法感到困惑。 以下是我的想法: 既然我们可以使用DFS遍历来自GC根的每个引用,为什么我们需要记住集合? 因为所有的博客都说我们使用的原因是,我们不需要检查每个区域,看看是否有一个对象被GC-Roots引用

  • A我是Drools的新手,读过一些文档和教程,当然,我的问题有一个简单的解决方案。我使用onle规则文件和类计数器,如下所示。环境为:Wintel JDK 1.7(71),DROOLS 6.1.0 有个规矩 这是kModule 跑步的结果 我的问题是: > 为什么“反淋浴1”规则只涉及最后插入的事实?是否存在任何隐藏行为? 3、为什么在count==1的retract object Counter

  • 问题内容: Java应该没有内存泄漏,但是仍然可能。当我的程序出现内存泄漏时,我可以修复它(我希望)。但是,当某些第三方程序包具备该功能时,我该怎么办?几乎什么都没有,除非不使用此软件包。 还有其他解决方案吗?我喜欢沙盒的想法。您被允许在某个区域内做任何您想做的事情,而您的“身体上的”没有能力打扰其他人。有没有办法为Java中的内存使用创建此类沙箱?想象一下=创建用于内存使用的沙箱,允许某些程序包