【问题标题】:Does Linux malloc() behave differently on ARM vs x86?Linux malloc() 在 ARM 和 x86 上的行为是否不同?
【发布时间】:2013-11-21 00:36:26
【问题描述】:

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

我的发现很详细here,但是 快速总结是:在我的桌面系统上,demo3 程序来自 文章似乎表明malloc() 总是 谎报内存量 分配——即使交换被禁用。例如,它愉快地“分配” 3 GB 的 RAM,然后在程序开始实际运行时调用 OOM 杀手 写入所有的内存。禁用交换后,将调用 OOM 杀手 在写入仅 610 MB 的 3 GB malloc() 之后 可用。

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

#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_memoryovercommit_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

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

【问题讨论】:

  • 你应该禁用memory overcommit
  • @BasileStarynkevitch,我们很可能会选择这样做,但我们想了解为什么在两个系统的设置相似时,该程序的行为会有所不同。
  • 您将Ubuntu eglibcARM glibc 进行比较。该软件可能会做一些不同的事情。同样,您的内核版本也完全不同。通常,ARM 在几个版本的功能上落后于 x86。你在比较完全不同的东西。 ARM 内部没有任何东西可以阻止这种情况。但所涉及的软件堆栈很大,差异很大。
  • 我知道,这是问题的症结所在:具体来说有什么不同?您链接到的 malloc.c 文件(感谢那些!)是相同的,所以它不是那个文件。 ARM 实现是否以某种方式绕过了内核过度使用设置(如果这甚至可能的话),还是demo3 程序没有比承诺的其他原因更早触发 OOM 杀手?
  • 我不认为 malloc 实现是问题的症结所在,因为 eglibc 和 glibc 实现都基于以下内容: * 版本 ptmalloc2-20011215 基于:版本 2.7.0 Sun 2001 年 3 月 11 日 14:14:06 Doug Lea (dl at gee) GLIBC, EGLIBC

标签: linux embedded arm malloc


【解决方案1】:

一点背景

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

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

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

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

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

好的,现在你的程序已经成功地malloc'ed了一些内存,但事实是没有实际分配物理内存的单个页面(x86 中为 4KB)根据您的要求(好吧,这是过于简单化了,因为一些页面本来可以用来存储有关内存池状态的信息,但这样更容易说明这一点)。

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

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

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

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

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

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

多少过量使用才足够?

在计算中,资源管理是一个复杂的问题。你有广泛的策略,从最严格的基于能力的系统,到更宽松的内核行为,如 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 处停止了。这可能会让人感到意外,但如果您查看它的内核配置 (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 拆分,因此系统运行正常。

【讨论】:

  • 从技术上讲,它不是分段错误,而是 页面错误 导致实际内存被附加。
猜你喜欢
  • 1970-01-01
  • 2014-05-06
  • 1970-01-01
  • 2020-03-21
  • 1970-01-01
  • 1970-01-01
  • 2020-01-04
  • 2017-09-20
  • 2016-04-17
相关资源
最近更新 更多