linux 上的 Malloc 没有过度使用

Malloc on linux without overcommitting

如何在不过度使用的情况下在 Linux 上分配内存,以便 malloc 实际上 returns NULL 如果没有内存可用并且进程不会在访问时随机崩溃?

我对 malloc 工作原理的理解:

  1. 分配器检查空闲列表是否有空闲内存。如果是,则分配内存。
  2. 如果没有,新页面从内核分配。这就是过度使用可能发生的地方。然后新内存returned.

因此,如果有一种方法可以从内核中获取由物理内存立即支持的内存,分配器可以使用它而不是获取过度使用的页面,并且 return NULL 如果内核拒绝提供更多内存。

有没有办法做到这一点?

更新:

我知道这不能完全保护进程免受 OOM 杀手的攻击,因为如果它的分数不好,它仍然会在内存不足的情况下被杀死,但这不是我担心的。

更新二: 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 的评论提出的新想法:

  1. 检查是否有足够的内存可用。据我了解,必须在 MemFreeSwapFree 值中检查 /proc/meminfo
  2. 仅当有足够的 space 可用(加上额外的安全余量)时,才分配内存。
  3. getpagesize 找出页面大小,并在每个页面大小的内存中写入一个字节,以便它得到物理内存(RAM 或交换)的支持。

我还仔细查看了 mmap(2) 并发现了以下内容:

MAP_NORESERVE

Do not reserve swap space for this mapping. When swap space is reserved, one has the guarantee that it is possible to modify the mapping. When swap space is not reserved one might get SIGSEGV upon a write if no physical memory is available. See also the discussion of the file /proc/sys/vm/overcommit_memory in proc(5). In kernels before 2.6, this flag only had effect for private writable

这是否意味着使用 ~MAP_NORESERVE 映射将完全保护进程免受 OOM 杀手的攻击?如果是这样,这将是完美的解决方案,只要有一个 malloc 实现,它可以直接在 mmap 之上工作。 (也许是 jemalloc?)

更新四: 我目前的理解是 ~MAP_NORESERVE 不会防止 OOM 杀手,但至少可以防止首次写入内存时发生段错误。

从评论的讨论来看,好像是在呼唤

mlockall( MCL_CURRENT | MCL_FUTURE );

在进程启动时将满足 malloc() 到 return NULL 的要求,而系统实际上无法提供内存。

根据 the Linux mlockall() man page:

mlockall() and munlockall()

mlockall() locks all pages mapped into the address space of the calling process. This includes the pages of the code, data and stack segment, as well as shared libraries, user space kernel data, shared memory, and memory-mapped files. All mapped pages are guaranteed to be resident in RAM when the call returns successfully; the pages are guaranteed to stay in RAM until later unlocked.

The flags argument is constructed as the bitwise OR of one or more of the following constants:

   MCL_CURRENT Lock all pages which are currently mapped into the
               address space of the process.

   MCL_FUTURE  Lock all pages which will become mapped into the address
               space of the process in the future.  These could be, for
               instance, new pages required by a growing heap and stack
               as well as new memory-mapped files or shared memory
               regions.

   MCL_ONFAULT (since Linux 4.4)
               Used together with MCL_CURRENT, MCL_FUTURE, or both.
               Mark all current (with MCL_CURRENT) or future (with
               MCL_FUTURE) mappings to lock pages when they are faulted
               in.  When used with MCL_CURRENT, all present pages are
               locked, but mlockall() will not fault in non-present
               pages.  When used with MCL_FUTURE, all future mappings
               will be marked to lock pages when they are faulted in,
               but they will not be populated by the lock when the
               mapping is created.  MCL_ONFAULT must be used with either
               MCL_CURRENT or MCL_FUTURE or both.

If MCL_FUTURE has been specified, then a later system call (e.g., mmap(2), sbrk(2), malloc(3)), may fail if it would cause the number of locked bytes to exceed the permitted maximum (see below). In the same circumstances, stack growth may likewise fail: the kernel will deny stack expansion and deliver a SIGSEGV signal to the process.

请注意,以这种方式使用 mlockall() 可能会产生其他意想不到的后果。 Linux 是在假设内存过量使用可用的情况下开发的,因此在 mlockall() 之后调用 fork() 之类的简单操作可能 运行 会出现问题。

How can I allocate memory on Linux without overcommitting

那是 loaded question,或者至少是不正确的。该问题基于错误的假设,这使得回答所述问题充其量是无关紧要的,最坏的情况是误导。

Memory overcommitment 是一个系统范围的策略——因为它决定了有多少虚拟内存可供进程使用——而不是进程可以自己决定的东西。

内存是否超载由系统管理员决定。在 Linux 中,该策略是非常可调的(参见 man 5 proc 中的 /proc/sys/vm/overcommit_memory进程在此期间无能为力 会影响内存过量使用策略的分配.

OP 似乎也有兴趣让他们的进程对 Linux 中的内存不足杀手(OOM 杀手)免疫。 (Linux 中的 OOM 杀手是一种用于缓解内存压力的技术,通过终止进程,从而将它们的资源释放回系统。)

这也是一种不正确的方法,因为 OOM 杀手是一个启发式过程,其目的不是 "punish or kill badly behaving processes",而是保持系统运行。此工具在 Linux 中也非常可调,系统管理员甚至可以调整每个进程在高内存压力情况下被杀死的可能性。除了进程使用的内存量,在内存不足的情况下,OOM 杀手是否会杀死它并不取决于进程;这也是由系统管理员管理的政策问题,而不是流程本身。

我认为 OP 试图解决的实际问题是如何编写 Linux 可以动态响应内存压力的应用程序或服务,而不仅仅是死机(由于 SIGSEGV 或 OOM杀手)。答案是你不需要——你让系统管理员担心什么对他们来说是重要的,而不是在他们的工作量中——除非你的应用程序或服务是一个它使用大量内存,因此很可能在高内存压力下被不公平地杀死。 (特别是如果数据集足够大,需要启用比以其他方式启用的交换量大得多的交换量,从而导致更高的交换风暴风险和后期但太强的 OOM 杀手。)

解决方案,或者至少是可行的方法,是内存锁定关键部分(甚至整个 application/service,如果它适用于不应交换到磁盘的敏感数据),或者使用带有专用备份文件的内存映射。 (对于后者,here 是我在 2011 年写的一个示例,它处理 TB 大小的数据集。)

OOM killer 仍然可以杀死进程,并且 SIGSEGV 仍然会发生(由于内核无法提供 RAM 支持的库函数的内部分配),除非所有应用程序都被锁定到 RAM ,但至少 service/process 不再是 不公平的 目标,只是因为它使用大量内存。

可以捕获 SIGSEGV 信号(当没有内存可用于支持虚拟内存时发生),但到目前为止,我还没有看到可以保证代码复杂性和所需维护工作的用例。

总而言之,对上述问题的正确回答是不,不要那样做