Linux - 内存不足

问题描述 投票:0回答:3

我试图通过手动调用来查看 oom_kill 的工作情况。

我动态分配内存,并尝试首先使用 while 循环无限使用它们,然后使用 for 循环来测试内存不足。

但是在第一种情况下,我使用 while 循环,它在没有交换的情况下抛出分段错误,并且在使用交换时变得无响应,而在调用 for 循环内存不足(oom_kill)时。

两者的示例代码:

第一种情况: while:

int main (void) {
char* p;
        while (1) {
                p=malloc(1<<20);
                memset (p, 0, (1<<20));       
                }
               }

第二种情况:对于:

int main (void) {
        int i, n = 0;
        char *pp[N];

        for (n = 0; n < N; n++) {
                pp[n] = malloc(1<<20);
                if (pp[n] == NULL)
                        break;
        }
        printf("malloc failure after %d MiB\n", n);

        for (i = 0; i < n; i++) {
                memset (pp[i], 0, (1<<20));
                printf("%d\n", i+1);
        }

其中 N 是调用 oom 的某个非常大的数字。对于第二种情况,参考了这个https://www.win.tue.nl/~aeb/linux/lk/lk-9.html

为什么会这样?我在 while 循环中犯了什么错误?

内核版本:4.15

c linux memory-management linux-kernel out-of-memory
3个回答
3
投票

为什么会这样?

要调用 OOM Killer,您需要遇到无法完成对内存的访问的情况,因为没有足够的 RAM 来完成访问。为此,您需要首先进行大量分配(虚拟内存映射),然后写入它们。

触发 OOM Killer 的过程非常简单:

  1. 分配大量内存

  2. 写入分配的内存

    在内核调用 OOM 杀手以提供更多 RAM/交换空间来填充之前,您必须有足够的预分配内存,以导致从 RAM 中驱逐的所有内容(例如内存映射文件),并使用所有交换空间正在写入的虚拟内存的支持。

我在 while 循环中犯了什么错误?

一个错误,一个逻辑错误。

错误是,您不检查

malloc()
是否返回 NULL。当进程没有更多可用虚拟内存(或内核出于任何原因拒绝提供更多虚拟内存)时,
malloc()
返回 NULL。 (在正常操作中,每个进程可用的虚拟内存对于非特权用户来说是有限的;运行例如
ulimit -a
查看当前限制。)

因为您在分配内存时立即访问内存,所以当 RAM 和 SWAP 耗尽时,内核只是拒绝允许您的进程进行更多操作,并且

malloc()
返回 NULL。然后,您取消引用 NULL 指针(通过使用
memset(NULL, 0, 1<<20)
),这会导致分段错误。

逻辑问题是该方案不会触发内核 OOM 杀手。

请记住,为了触发内核 OOM 杀手,您的进程必须分配尚未访问的内存。仅当内核已经提供了虚拟内存,但无法用实际 RAM 来支持它时,才会调用 OOM 杀手,因为 RAM 中没有任何可逐出的内容,并且交换区已满。

在你的情况下,OOM杀手不会被唤起,因为当内核耗尽RAM和交换空间时,它可以简单地拒绝提供更多(虚拟内存),导致

malloc()
返回NULL。

(Linux 内核内存子系统是一个正在积极开发的子系统,因此您看到的确切行为取决于内核版本、RAM 和交换量以及内存管理器可调参数(例如,位于 /proc/sys/ 下的可调参数) vm/)。以上描述了最常见或典型的情况和配置。)


您也不需要外部阵列。例如,您可以将分配链接到链表:

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

#ifndef   SIZE
#define   SIZE  (2*1024*1024)  /* 2 MiB */
#endif

struct list {
    struct list *next;
    size_t       size;
    char         data[];
}

struct list *allocate_node(const size_t size)
{
    struct list *new_node;

    new_node = malloc(sizeof (struct list) + size);
    if (!new_node)
        return NULL;

    new_node->next = NULL;
    new_node->size = size;
}

int main(void)
{
    size_t        used = 0;
    struct list  *root = NULL, *curr;

    /* Allocate as much memory as possible. */
    while (1) {
        curr = allocate_node(SIZE - sizeof (struct list));
        if (!curr)
             break;

        /* Account for allocated total size */
        used += SIZE;

        /* Prepend to root list */
        curr->next = root;
        root = curr;
    }

    printf("Allocated %zu bytes.\n", used);
    fflush(stdout);

    /* Use all of the allocated memory. */
    for (curr = root; curr != NULL; curr = curr->next)
        if (curr->size > 0)
            memset(curr->data, ~(unsigned char)0, curr->size);

    printf("Wrote to %zu bytes of allocated memory. Done.\n", used);
    fflush(stdout);

    return EXIT_SUCCESS;
}

注意,上面的代码未经测试,甚至未经编译,但逻辑是合理的。如果您发现其中存在错误,或者有其他问题,请在评论中告诉我,以便我进行验证和修复。


2
投票

您正在阅读的文档来自 2003 年。它选择分配的大得难以置信的数字是 10,000 MiB。

在 2018 年的今天,当新计算机可能配备 16GiB RAM 时,这种分配绝对可以成功,不会出现任何问题。


2
投票

我在 while 循环中犯了什么错误?

分段错误可能是向 memset() 传递空指针的结果,因为 malloc() 出错时将返回 NULL。

您的第二个示例通过始终检查 malloc() 的返回值来避免此错误。

我使用了 while 循环...它对交换没有响应...

来自您提到的您正在阅读的文件:

有时进程在访问内核无法提供的内存时会出现段错误,有时它们被杀死,有时其他进程被杀死,有时内核挂起

除了提到内核版本之外,您对操作系统和系统的描述非常模糊。大概这是 32 位版本?

内存不足实际上有两种方法。您的程序可能超出分配的(虚拟)内存量,或者系统实际上可能用完内存页。 请注意,内存(页)的可用性是物理内存大小、交换空间大小、内存使用情况和进程负载的复杂组合。
参考:当 Linux 内存不足时,作者:Mulyadi Santosa 或此处

© www.soinside.com 2019 - 2024. All rights reserved.