32位内核中的无限循环malloc,具有4 Gb RAM和10 Gb交换分区

时间:2016-09-19 17:43:00

标签: c linux memory-management linux-kernel operating-system

我们说我有一个32位内核。 4 Gb RAM,10 Gb交换分区。

我有一个在无限循环中有malloc的进程。因此,最终系统的OOM会终止这个过程。这是两个论点。

参数1:因为,它的32位内核具有3:1的虚拟地址分割,即用户空间为3Gb,内核空间为1 Gb。该进程将分配3 Gb的内存,然后没有更多的内存给予。因此,OOM会杀死这个过程。

参数2:因为,它的32位内核具有3:1的虚拟地址分割,即用户空间为3Gb,内核空间为1 Gb。这个过程将被分配4 Gb的内存,然后就没有更多的内存了。因此,OOM会杀死这个过程。

参数3:Malloc首先占用Ram的4 Gb内存,然后需要10 Gb的Swap分区然后OOM会终止进程。

这些参数中的哪一个(如果有的话)是真的?

论坛上还有其他答案,但我不确定这个问题的答案是否取决于它的32位内核还是64位内核?

2 个答案:

答案 0 :(得分:2)

为什么认为OOM会杀死这个过程?在您的示例中,您将耗尽地址空间,而不是耗尽所有实际内存。因此,在您的示例(32位)中,您将能够分配大约3 GB的地址空间(减去文本/数据/堆栈和其他段以及可能的内存碎片),然后系统将返回ENOMEM。

如果您使用的是64位系统,事情会变得更有趣。现在结果将在很大程度上取决于你是否真的使用这个内存。如果您不使用它(仅分配),那么由于过度使用(当然,如果您没有禁用它),您将能够分配大量的地址空间,但如果您尝试使用它,在那里(大约10 + 4 GB边界)你会触发OOM并且会杀死程序。

更新

使用这样的程序检查实际上很容易:

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

int main()
{
    static char *allocs[10 * 1024 * 1024];
    static unsigned int size;
    unsigned int i;
    unsigned int const chunk = 1024 * 1024;

    for (i = 0; i < (sizeof(allocs)/sizeof(allocs[0])); i++)
        if ((allocs[i] = malloc(chunk)) == NULL) {
            printf("failed to allocate after %u MB: %s\n", i, strerror(errno));
            break;
        }

    size = i;
    if (size == (sizeof(allocs)/sizeof(allocs[0])))
         printf("allocated %u MB successfully!\n", size);
    for (i = 0; i < size; i++) {
        memset(allocs[i], 1, chunk);
        if (i % 100 == 0)
            printf("memset %u MB\n", i);
    }
    return 0;
}

它试图分配10M的内存块,每块大小为1M,因此有效地分配了10TB。在具有4 GB RAM的32位系统上,它给出了这个结果(缩短了一点):

failed to allocate after 3016 MB: Cannot allocate memory
memset 0 MB
memset 100 MB
...
memset 2900 MB
memset 3000 MB

正如预期的那样,没有足够的地址空间并且添加交换(我用5GB而不是10来完成我的测试)并没有任何帮助。

对于64位,它的行为有点出乎意料(这是没有交换,只有4 GB的RAM)因为它不会输出任何东西,只是在执行分配时被OOM杀手杀死。但是可以解释看OOM杀手日志:

[  242.827042] [ 5171]  1000  5171 156707933   612960  306023        0             0 a.out
[  242.827044] Out of memory: Kill process 5171 (a.out) score 905 or sacrifice child
[  242.827046] Killed process 5171 (a.out) total-vm:626831732kB, anon-rss:2451812kB, file-rss:28kB

因此它能够分配大约620 GB(!)的虚拟内存,并且真正映射了2.4。还要记住,我们要用malloc()进行分配,因此C库必须自己管理,即使你估计它有大约0.5%的开销,如此大的分配,你只能得到大量的数据。 (为了免除这种开销,你可以尝试使用mmap())。在这个阶段,我们有足够的内存用于缺少它,因此进程在仍然进行分配时被杀死。如果您不那么贪婪并将分配更改为[100 * 1024](实际上只有100 GB),您可以轻松地看到交换的效果,因为如果没有它在4 GB系统上,您可以:

allocated 102400 MB successfully!
memset 0 MB
memset 100 MB
...
memset 2800 MB
memset 2900 MB
Killed

添加了5 GB的交换:

allocated 102400 MB successfully!
memset 0 MB
memset 100 MB
...
memset 7900 MB
memset 8000 MB
Killed

一切如预期。

答案 1 :(得分:0)

让我们先稍微澄清一下您的要求。您想要运行32位进程。它正在紧密循环中分配内存并进行更改。最后一部分是必要的,否则只保留地址空间,但没有内存提交给它。

您的程序将消耗资源直到32位虚拟地址空间的可用部分。对于x86上的大多数32位UNIX类内核,这将是3GB,因为前1GB是为内核本身保留的。 Linux有一个构建时选项,可将其更改为2GB:2GB分割。如果您在Xen下运行,则内核使用单独的地址空间,因此您的程序实际上可以使用完整的4GB。其他CPU架构通常介于这两种情况之间。

既然我们已经确定程序将使用3GB到4GB的内存,那么让我们看一下内核可用的资源。如果您有10GB交换,它总是可以将足够的页面推送到交换中以保持程序运行。假设没有其他程序运行大量分配,你将永远不会遇到OOM杀手,因为你没有首先耗尽内存。

关于OOM杀手的最后一句话。如果您没有更改分配的内存并运行程序的多个实例(例如&gt; 5),那么它们仍然不会消耗超过几MB的实际内存(RAM +交换)。这就是所谓的内存过度使用。现在,如果程序开始更改分配,它们将慢慢减少可用的RAM +交换并在某些时候耗尽。这是OOM杀手触发时的情况。如果没有内存过量使用,只要所有正在运行的进程的大小总和大于RAM + swap,malloc就会返回错误。这稍微简化了,因为某些类型的内存映射不算数。