Linux内存不足

时间:2018-07-27 03:07:56

标签: c linux memory-management linux-kernel out-of-memory

我试图通过手动调用来查看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

3 个答案:

答案 0 :(得分:2)

您正在阅读的文档来自2003年。它选择分配的数量过大是10,000 MiB。

今天,在2018年,当新计算机可能配备16GiB RAM时,这种分配肯定可以毫无问题地成功。

答案 1 :(得分:2)

  

while循环在犯什么错误?

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

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

  

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

从您提到的正在阅读的文档中:

有时进程在访问内核无法提供的内存时遇到段错误,有时它们被杀死,有时其他进程被杀死,有时内核挂起 < / p>

除了提及内核版本外,您对操作系统和系统说明也很含糊。大概是32位版本?

实际上有两种耗尽内存的方法。您的程序可能超出了分配的(虚拟)内存量,否则系统实际上可能会用完内存页面。 请注意,内存(页面)的可用性是物理内存大小,交换空间大小,内存使用情况和进程负载的复杂组合。
参考:When Linux Runs Out of Memory ,由Mulyadi Santosa或here

答案 2 :(得分:2)

  

为什么会这样?

要调用OOM杀手,您需要遇到一种情况,即由于没有足够的RAM来实现访问而无法实现对内存的访问。为此,您首先要分配较大的空间(虚拟内存映射),然后将其写入。

触发OOM杀手的过程非常简单:

  1. 分配大量内存

  2. 写入分配的内存

    在内核将唤起OOM杀手提供更多RAM /交换空间之前,您必须具有足够的预分配内存以使所有可从RAM收回的东西(如内存映射的文件)被收回,并使用所有交换。来完成对要写入的虚拟内存的备份。

  

while循环在犯什么错误?

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

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

由于分配后您立即访问内存,因此内核仅拒绝在RAM和SWAP用尽时允许您的进程更多,而malloc()返回NULL。然后,您取消引用NULL指针(使用memset(NULL, 0, 1<<20)),这会导致分段错误。

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

请记住,为了触发内核OOM杀手,您的进程必须分配尚未访问的内存。内核仅在已经提供虚拟内存时才调用OOM杀手,但无法将其与实际RAM一起备份,因为RAM中没有可收回的东西,并且交换已满。

在您的情况下,OOM杀手将不会被唤醒,因为当内核用尽RAM并进行交换时,它可以简单地拒绝提供更多(虚拟内存),从而导致malloc()返回NULL。

(Linux内核内存子系统是一个积极开发的子系统,因此,您看到的确切行为取决于内核版本,RAM和交换的数量以及内存管理器可调参数(例如,{{3}下的可调参数) } ..以上介绍了最常见的或典型情况和配置。)


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

#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;
}

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