此代码段每次读取字母时都会分配2Gb' u'来自stdin,并且会在读取所有已分配的字符后对其进行初始化' 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操作没有反映在资源上。
例如,当我输入&#39; u&#39;只有一次(即分配2GB的堆内存),我在htop中看不到内存使用量增加了2GB。只有当我输入&#39;(即初始化)时,我才会看到内存使用量的增加。
因此,我能够&#34; malloc&#34;比存在更多的堆内存。例如,我可以使用malloc 6GB(比我的内存和交换内存更多)和malloc允许它(即malloc不返回NULL)。但是当我尝试初始化分配的内存时,我可以看到内存和交换内存已经填满,直到进程被终止。
- 我的问题:
1.这是一个内核错误吗?
2.有人可以向我解释为什么允许这种行为?
答案 0 :(得分:12)
它被称为memory overcommit。您可以通过以root身份运行来禁用它:
echo 2 > /proc/sys/vm/overcommit_memory
并且它不是我喜欢的内核功能(所以我总是禁用它)。请参阅malloc(3)和mmap(2)以及proc(5)
注意:echo 0
代替echo 2
经常 - 但并非总是如此。阅读文档(特别是我刚刚链接到的proc
手册页。)
答案 1 :(得分:10)
来自man malloc
(online here):
默认情况下,Linux遵循乐观的内存分配策略。 这意味着当malloc()返回非NULL时,无法保证 记忆确实可用。
所以,当你只是想分配太多时,它“谎言”给你,当你想使用分配的内存时,它会尝试为你找到足够的内存,如果它找不到足够的内存它可能会崩溃
答案 2 :(得分:5)
不,这不是内核错误。您发现了一些称为延迟分页(或过度使用)的内容。
在你用malloc (...)
分配的地址写一个字节之前,内核只会“保留”地址范围。这当然取决于内存分配器和操作系统的实现,但是在首次使用内存之前,大多数好的内核都不会产生大部分内核开销。
囤积分配器是一个立即想到的大罪犯,通过广泛的测试我发现它几乎从不利用支持延迟分页的内核。如果在分配后立即对整个内存范围进行零填充,则可以始终减轻任何分配器中延迟分页的影响。
像VxWorks这样的实时操作系统永远不会允许这种行为,因为后期分页会引入严重的延迟。从技术上讲,它所做的只是将延迟关闭,直到稍后的不确定时间。
有关更详细的讨论,您可能有兴趣了解IBM的AIX操作系统如何处理page allocation和overcommitment。
答案 3 :(得分:3)
这是Basile提到的结果,而不是提交内存。但是,这种解释很有意思。
基本上,当您尝试在Linux(POSIX?)中映射其他内存时,内核将保留它,并且只有在您的应用程序访问其中一个保留页面时才会实际使用它。这允许多个应用程序保留超过实际的ram / swap总量。
这在大多数Linux环境中都是理想的行为,除非你有一个实时操作系统,或者你确切知道谁将需要什么资源,何时以及为什么。
否则有人可能会来,将所有内存(没有实际做任何事情)和你的应用程序进行操作。
这个延迟分配的另一个例子是mmap(),你有一个虚拟地图,你正在映射的文件可以放在里面 - 但你只有少量的实际内存专用于工作。这允许你mmap()巨大的文件(大于你可用的RAM),并像普通的文件句柄一样使用它们很漂亮)
-n
答案 4 :(得分:3)
初始化/使用内存应该有效:
memset(m, 0, bytes);
此外,您可以使用calloc
,不仅可以分配内存,还可以为您填充零:
char* m = (char*) calloc(1, bytes);
答案 5 :(得分:1)
1.这是一个内核错误吗?
没有
2.有人可以向我解释为什么允许这种行为?
有几个原因:
缓解需要知道最终的内存需求 - 让应用程序能够获得一个它认为是上限的内存量通常很方便需要它可能实际上有。例如,如果它正在准备某种类型的报告,或者只是计算报告的最终大小的初始通过,或者是连续更大区域的realloc()(具有必须复制的风险)可能会使代码复杂化并损害性能,其中 - 将每个条目的一些最大长度乘以条目数可以非常快速和容易。如果您知道虚拟内存相对丰富,就您的应用程序的需求而言,那么更大的虚拟地址空间分配非常便宜。
稀疏数据 - 如果您有虚拟地址空间备用,能够拥有稀疏数组并使用直接索引,或者分配哈希表大容量()到尺寸()的比例,可以导致一个非常高性能的系统。当数据元素大小是内存分页大小的倍数时,两者都工作得最好(在具有低开销/浪费和有效使用内存缓存的意义上),或者失败那么大或小的整数部分。
资源共享 - 考虑一个ISP向建筑物内的1000名消费者提供“每秒1千兆位”的连接 - 他们知道如果所有消费者同时使用它们将获得大约1兆位,但依靠他们的实际经验,虽然人们要求1千兆位并且在特定时间想要它的很大一部分,但是不可避免地会有一些更低的最大值和更多并发使用的平均值较低。应用于内存的相同见解允许操作系统支持比其他方式更多的应用程序,并且在满足期望的情况下获得合理的平均成功。由于共享的Internet连接速度随着更多用户同时发出的需求而降低,因此从磁盘上的交换内存进行分页可能会启动并降低性能。但是与互联网连接不同,交换内存是有限制的,并且如果所有应用程序确实尝试同时使用内存以超出限制,则有些将开始获取信号/中断/陷阱报告内存耗尽。总而言之,启用此内存过量使用行为后,只需检查malloc()
/ new
返回的非NULL指针不足以保证物理内存实际可用,并且程序可能仍会在以后收到信号它试图使用内存。