【发布时间】:2018-07-13 02:18:31
【问题描述】:
如何在不过度使用的情况下在 Linux 上分配内存,以便 malloc 在没有可用内存且进程在访问时不会随机崩溃时实际返回 NULL?
我对 malloc 工作原理的理解:
- 分配器检查空闲列表是否有空闲内存。如果是,则分配内存。
- 如果否,则从内核分配新页面。这将是可能发生过度使用的地方。然后返回新的内存。
因此,如果有一种方法可以从内核获取由物理内存立即支持的内存,那么分配器可以使用该方法而不是获取过度使用的页面,并在内核拒绝提供更多内存时返回 NULL。
有没有办法做到这一点?
更新:
我知道这不能完全保护进程免受 OOM 杀手的影响,因为如果分数不好,它仍然会在内存不足的情况下被杀死,但这不是我担心的。
更新 2:
Nominal Animal 的评论让我有了以下使用mlock的想法:
void *malloc_without_overcommit(size_t size) {
void *pointer = malloc(size);
if (pointer == NULL) {
return NULL;
}
if (mlock(pointer, size) != 0) {
free(pointer);
return NULL;
}
return pointer;
}
但是由于所有的系统调用,这可能很慢,所以这可能应该在分配器实现级别完成。而且它还阻止了使用交换。
更新 3:
遵循 John Bollingers 的新想法:
- 检查是否有足够的内存可用。据我了解,这必须在
/proc/meminfo的MemFree和SwapFree值中进行检查。 - 仅当有足够的可用空间(加上额外的安全余量)时,才分配内存。
- 使用
getpagesize找出页面大小,并在每个页面大小的内存中写入一个字节,以便它得到物理内存(RAM 或交换)的支持。
我还更仔细地查看了mmap(2) 并发现了以下内容:
MAP_NORESERVE
不要为此映射保留交换空间。保留交换空间时,可以保证可以修改映射。如果没有保留交换空间,如果没有可用的物理内存,可能会在写入时获得 SIGSEGV。另请参见 proc(5) 中对文件 /proc/sys/vm/overcommit_memory 的讨论。在 2.6 之前的内核中,这个标志只对私有可写有效
这是否意味着使用 ~MAP_NORESERVE 进行映射将完全保护进程免受 OOM 杀手的攻击?如果是这样,这将是一个完美的解决方案,只要有一个malloc 实现,它可以直接在mmap 之上工作。 (也许是 jemalloc?)
更新 4:
我目前的理解是~MAP_NORESERVE 不会保护 OOM 杀手,但至少可以防止第一次写入内存时出现段错误。
【问题讨论】:
-
@NominalAnimal 如果没有 [overcommit],虚拟内存将限制为总 RAM。 可用交换空间也会增加可用虚拟内存。
-
mlock(pointer, size)可能无法使用 -mlock()将锁定页面,而您仍在使用malloc()。您还必须尝试以某种方式跟踪需要解锁的页面,因为munlock()也对整个页面进行操作。 -
@FSMaxB
free()不必“回馈”任何东西。一旦将堆内存分配给您的进程,您的进程通常会永远保留它。 Linux 上的标准堆例程在底层确实使用了混合模式分配器,但是,较大的分配可能会通过专用的mmap()调用来满足,而较小的分配可能会使用sbrk()/brk()-obtained RAM 或 @987654343 @ 记忆。 Linux 的混合模式分配器确实使解决您的特定问题变得更加困难。 -
如果可能,您可以通过将 sysctl
vm.overcommit_memory设置为 2 来禁用整个系统的过度使用。 -
我明确不想在整个系统中关闭过度使用。 -- 那有什么意义呢?内存过量使用是整个系统的问题。您无法在每个进程的基础上有效地避免它,因为即使您的进程的分配在没有 ovecommit 的情况下成功,任何进程的下一次分配可能会使系统进入过度使用状态,从而影响您的进程任何其他。
标签: c linux memory-management memory-overcommitment