3

这个网站上有很多关于内存分配的问题,但我找不到一个专门解决我问题的问题。这个问题 似乎最接近,它让我看到了这篇文章,所以......我比较了它包含的三个演示程序在(虚拟)桌面 x86 Linux 系统和基于 ARM 的系统上的行为。

我的发现在这里详细介绍,但快速总结是:在我的桌面系统上,demo3文章中的程序似乎malloc() 总是显示分配的内存量 - 即使禁用了交换。例如,它愉快地“分配”了 3 GB 的 RAM,然后在程序开始实际写入所有内存时调用 OOM 杀手。在禁用交换的情况下,OOM 杀手在写入malloc()据称可用的 3 GB 中的仅 610 MB 后被调用。

演示程序的目的是演示 Linux 的这个有据可查的“特性”,所以这一切都不足为奇。但是我们基于 i.MX6 的嵌入式目标在工作中的行为是不同的,它malloc()似乎在讲述它分配多少 RAM 的真相(?)下面的程序(从文章中逐字复制)总是在第二个循环时i == n

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

#define N       10000

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

        return 0;
}

所以我的问题,简而言之,是:为什么demo3程序——或其他一些不幸的 OOM 杀手受害者——总是i == n在我的桌面系统上很久以前就被杀死(暗示这malloc()是一个骗子),但它只有在i == n打开时才会被杀死我们的 i.MX6 ARM 目标(暗示这malloc()可能是实话)?这种差异是 libc 和/或内核版本的功能,还是其他原因?如果在此目标上分配失败, 我是否可以得出结论,malloc()始终返回 NULL?

注意:每个系统的一些细节(请注意,overcommit_memory两者overcommit_ratio的值相同):

# Desktop system
% uname -a
Linux ubuntu 3.8.0-33-generic #48-Ubuntu SMP Wed Oct 23 17:26:34 UTC 2013 i686 i686 i686 GNU/Linux
% /lib/i386-linux-gnu/libc.so.6 
GNU C Library (Ubuntu EGLIBC 2.17-0ubuntu5.1) stable release version 2.17, by Roland McGrath et al.
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.7.3.
Compiled on a Linux 3.8.13 system on 2013-09-30.
Available extensions:
    crypt add-on version 2.1 by Michael Glad and others
    GNU Libidn by Simon Josefsson
    Native POSIX Threads Library by Ulrich Drepper et al
    BIND-8.2.3-T5B
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/eglibc/+bugs>.
% cat /proc/sys/vm/overcommit_memory
0
% cat /proc/sys/vm/overcommit_ratio 
50

# i.MX6 ARM system
# uname -a
Linux acmewidgets 3.0.35-ts-armv7l #2 SMP PREEMPT Mon Aug 12 19:27:25 CST 2013 armv7l GNU/Linux
# /lib/libc.so.6
GNU C Library (GNU libc) stable release version 2.17, by Roland McGrath et al.
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.7.3.
Compiled on a Linux 3.0.35 system on 2013-08-14.
Available extensions:
    crypt add-on version 2.1 by Michael Glad and others
    Native POSIX Threads Library by Ulrich Drepper et al
    BIND-8.2.3-T5B
libc ABIs: UNIQUE
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.
# cat /proc/sys/vm/overcommit_memory
0
% cat /proc/sys/vm/overcommit_ratio 
50

背景:我们正在尝试决定如何在面向媒体的嵌入式应用程序中处理内存不足的情况,并且想知道我们是否可以——对于这个特定的目标——信任malloc()在分配失败时提醒我们。我使用桌面 Linux 应用程序的经验让我认为答案肯定不是,但现在我不太确定。

4

1 回答 1

10

一点背景

malloc()不会说谎,您的内核虚拟内存子系统会说谎,这是大多数现代操作系统上的常见做法。当你使用malloc()时,真正发生的事情是这样的:

  1. libc 实现malloc()检查其内部状态,并尝试通过使用各种策略来优化您的请求(例如尝试使用预分配的块,分配比预先请求更多的内存......)。这意味着实现将影响性能并稍微改变内核请求的内存量,但这在检查“大数字”时并不真正相关,就像您在测试中所做的那样。

  2. 如果预先分配的内存块中没有空间(请记住,内存块通常很小,大约为 128KB 到 1MB),它会向内核请求更多内存。实际的系统调用因内核而异(mmap(), vm_allocate()...),但其目的基本相同。

  3. 内核的 VM 子系统将处理该请求,如果它发现它是“可接受的”(稍后将详细介绍此主题),它将在请求任务的内存映射中创建一个新条目(我使用的是 UNIX 术语,其中 task 是一个具有所有状态和线程的进程),并将所述映射条目的起始值返回给malloc()

  4. malloc()将记下新分配的内存块,并将适当的答案返回给您的程序。

好的,所以现在您的程序已经成功地分配了一些内存,但事实是,实际上还没有为您的请求分配物理内存的单个页面(x86 中为 4KB)(嗯,这是过于简单化了,因为附带地,一些页面可以用于存储有关内存池状态的信息,但它更容易说明这一点)。

那么,当您尝试访问这个最近分配过的内存时会发生什么?分段错误。令人惊讶的是,这是一个鲜为人知的事实,但您的系统一直在产生分段错误。然后你的程序被中断,内核控制,检查地址错误是否对应于一个有效的映射条目,获取一个或多个物理页面并将它们链接到任务的映射。

如果您的程序试图访问不在您的任务中的映射条目内的地址,内核将无法解决故障,并将向它发送信号(或非 UNIX 系统的等效机制)指出这个问题。如果程序自己不处理该信号,它将因臭名昭著的分段错误错误而被杀死。

所以物理内存不是在您调用时分配的malloc(),而是在您实际访问该内存时分配的。这允许操作系统执行一些漂亮的技巧,如磁盘分页气球过度使用。

这样,当您询问特定进程正在使用多少内存时,您需要查看两个不同的数字:

  • 虚拟大小:已请求的内存量,即使它实际上并未使用。

  • 驻留大小:它实际使用的内存,由物理页面支持。

多少过量使用就足够了?

在计算中,资源管理是一个复杂的问题。您有广泛的策略,从最严格的基于功能的系统,到更宽松的内核(如 Linux)行为(使用memory_overcommit == 0),这基本上允许您请求内存达到任务允许的最大映射大小(这是一个取决于架构的限制)。

在中间,您有像 Solaris(在您的文章中提到)这样的操作系统,它将任务的虚拟内存量限制为 ( physical pages + swap disk pages) 的总和。但是不要被您引用的文章所迷惑,这并不总是一个好主意。如果您正在运行一个 Samba 或 Apache 服务器,同时运行着成百上千个独立进程(这会导致由于碎片而浪费大量虚拟内存),您将不得不配置大量的交换磁盘,否则您的系统将耗尽虚拟内存,但仍有大量可用 RAM。

但是为什么内存过量使用在 ARM 上的工作方式不同呢?

它没有。至少不应该,但是 ARM 供应商有一种疯狂的倾向,即对他们随系统分发的内核进行任意更改。

在您的测试用例中,x86 机器按预期工作。当您以小块分配内存并且您已vm.overcommit_memory设置为 0 时,您可以填充所有虚拟空间(位于 3GB 行的某处),因为您在 32 位机器上运行它(如果您在 64 位上试试这个,循环将一直运行到 n==N)。显然,当您尝试使用该内存时,内核会检测到物理内存变得稀缺,并激活 OOM 杀手对策。

在 ARM 上应该是一样的。如果没有,我想到了两种可能性:

  1. overcommit_memory采用 NEVER (2) 策略,可能是因为有人在内核上强制采用这种方式。

  2. 您已达到任务允许的最大地图大小。

与在 ARM 上的每次运行一样,您会在malloc阶段获得不同的值,我会放弃第二个选项。确保overcommit_memory已启用(值 0)并重新运行您的测试。如果您可以访问这些内核源代码,请查看它们以确保内核尊重此sysctl(正如我所说,一些 ARM 供应商喜欢对他们的内核做一些讨厌的事情)。

作为参考,我在 QEMU 模拟 vertilepb 和 Efika MX (iMX.515) 上运行了 demo3。第一个在 3 GB 标记处停止malloc'ing,正如在 32 位机器上所预期的那样,而另一个更早地在 2 GB 处停止了 malloc'ing。这可能会让人感到意外,但是如果您查看它的内核配置(https://github.com/genesi/linux-legacy/blob/master/arch/arm/configs/mx51_efikamx_defconfig),您会看到这个:

CONFIG_VMSPLIT_2G=y
# CONFIG_VMSPLIT_1G is not set
CONFIG_PAGE_OFFSET=0x80000000

内核配置为 2GB/2GB 拆分,因此系统运行正常。

于 2014-02-23T10:29:24.743 回答