9

此代码片段将在每次从标准输入读取字母“u”时分配 2Gb,并在读取“a”后初始化所有分配的字符。

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#define bytes 2147483648
using namespace std;
int main()
{
    char input [1];
    vector<char *> activate;
    while(input[0] != 'q')
    {
        gets (input);
        if(input[0] == 'u')
        {
            char *m = (char*)malloc(bytes);
            if(m == NULL) cout << "cant allocate mem" << endl;
            else cout << "ok" << endl;
            activate.push_back(m);
        }
        else if(input[0] == 'a')
        {
            for(int x = 0; x < activate.size(); x++)
            {
                char *m;
                m = activate[x];
                for(unsigned x = 0; x < bytes; x++)
                {
                    m[x] = 'a';
                }
            }
        }
    }
    return 0;
}

我在具有 3Gb 内存的 linux 虚拟机上运行此代码。在使用 htop 工具监控系统资源使用情况时,我意识到 malloc 操作并没有反映在资源上。

例如,当我只输入 'u' 一次(即分配 2GB 的堆内存)时,我看不到 htop 中的内存使用量增加了 2GB。只有当我输入'a'(即初始化)时,我才看到内存使用量增加。

因此,我能够“malloc”比现有更多的堆内存。例如,我可以 malloc 6GB(这超过了我的 ram 和交换内存)并且 malloc 将允许它(即 malloc 不返回 NULL)。但是当我尝试初始化分配的内存时,我可以看到内存和交换内存已满,直到进程被终止。

-我的问题:

1.这是内核错误吗?

2.有人可以向我解释为什么允许这种行为吗?

4

6 回答 6

16

这称为内存过度使用。您可以通过以 root 身份运行来禁用它:

 echo 2 > /proc/sys/vm/overcommit_memory

它不是我喜欢的内核功能(所以我总是禁用它)。参见malloc(3)mmap(2)proc(5)

注意:echo 0而不是echo 2 经常- 但并非总是 - 也有效。阅读文档(特别proc是我刚刚链接到的手册页)。

于 2013-11-03T07:32:40.883 回答
10

来自man malloc在线here):

默认情况下,Linux 遵循乐观的内存分配策略。这意味着当 malloc() 返回非 NULL 时,不能保证内存确实可用。

因此,当您只想分配太多时,它会“撒谎”您,当您想使用分配的内存时,它会尝试为您找到足够的内存,如果找不到足够的内存,它可能会崩溃。

于 2013-11-03T07:37:45.913 回答
5

不,这不是内核错误。您发现了一种称为延迟分页(或过度使用)的东西。

在您向内核分配的地址写入一个字节之前,malloc (...)所做的只是“保留”地址范围。当然,这实际上取决于您的内存分配器和操作系统的实现,但大多数好的分配器在首次使用内存之前不会产生大部分内核开销。

囤积分配器是立即想到的一个大问题,通过广泛的测试,我发现它几乎从不利用支持延迟分页的内核。如果在分配后立即对整个内存范围进行零填充,则始终可以减轻任何分配器中延迟分页的影响。

像 VxWorks 这样的实时操作系统永远不会允许这种行为,因为延迟分页会引入严重的延迟。从技术上讲,它所做的只是将延迟推迟到以后不确定的时间。

如需更详细的讨论,您可能有兴趣了解 IBM 的 AIX 操作系统如何处理页面分配过度使用。

于 2013-11-03T07:48:41.113 回答
3

这是 Basile 提到的过度提交内存的结果。然而,解释有点有趣。

基本上,当您尝试在 Linux(POSIX?)中映射额外的内存时,内核只会保留它,并且只有在您的应用程序访问保留页面之一时才会真正使用它。这允许多个应用程序保留超过实际总量的 ram/swap。

这在大多数 Linux 环境中都是可取的行为,除非您有一个实时操作系统或您确切知道谁需要什么资源、何时以及为什么需要的东西。

否则有人可能会出现,malloc 了所有的 ram(实际上没有用它做任何事情)并 OOM 你的应用程序。

这种惰性分配的另一个例子是 mmap(),其中您有一个虚拟映射,您正在映射的文件可以放入其中 - 但您只有少量的实际内存专用于该工作。这允许您 mmap() 大文件(大于可用 RAM),并像使用普通文件句柄一样使用它们,这很漂亮)

-n

于 2013-11-03T07:40:36.033 回答
3

初始化/使用内存应该可以工作:

memset(m, 0, bytes);

您还可以使用calloc它不仅分配内存,还可以为您填充零:

char* m = (char*) calloc(1, bytes);
于 2013-11-03T07:46:27.460 回答
2

1.这是内核错误吗?

不。

2.有人可以向我解释为什么允许这种行为吗?

有几个原因:

  • 减少了解最终内存需求的需要——让应用程序能够达到它认为可能实际需要的上限的内存量通常很方便。例如,如果它正在准备某种类型的报告,无论是初始传递只是为了计算报告的最终大小,还是连续更大区域的 realloc() (具有必须复制的风险)都可能会使代码显着复杂化并损害性能,其中,将每个条目的某个最大长度乘以条目数可能非常快速和容易。如果您知道就应用程序的需求而言虚拟内存相对丰富,那么分配更大的虚拟地址空间非常便宜。

  • 稀疏数据——如果你有空闲的虚拟地址空间,能够拥有一个稀疏数组并使用直接索引,或者分配一个具有大容量()与大小()比率的哈希表,可以产生一个非常高性能的系统。当数据元素大小是内存分页大小的倍数时,两者都工作得最好(在具有低开销/浪费和有效使用内存缓存的意义上),或者失败的更大或很小的整数部分。

  • 资源共享- 考虑一个 ISP 为建筑物中的 1000 名消费者提供“每秒 1 吉比特”的连接 - 他们知道如果所有消费者同时使用它,他们将获得大约 1 兆比特,但依赖于他们的实际体验也就是说,尽管人们要求 1 GB 并且在特定时间想要其中的很大一部分,但不可避免地会有一些较低的最大值和低得多的平均并发使用量。应用于内存的相同见解使操作系统能够支持比其他方式更多的应用程序,并且在满足期望方面取得了合理的平均成功。就像共享 Internet 连接的速度会随着更多用户同时发出请求而降低一样,从磁盘上的交换内存进行分页可能会启动并降低性能。但与互联网连接不同的是,交换内存是有限制的,如果所有应用程序确实尝试同时使用内存以超过该限制,则某些应用程序将开始收到报告内存耗尽的信号/中断/陷阱。总之,启用此内存过度使用行为后,只需检查malloc()/new返回一个非 NULL 指针并不足以保证物理内存实际可用,并且程序在稍后尝试使用内存时仍可能收到信号。

于 2013-11-13T09:27:57.110 回答