Linux malloc()在ARM vs x86上的行为是否有所不同?

eva*_*low 2 linux embedded malloc arm

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

我的发现在这里详述,但快速摘要是:在我的桌面系统上,demo3文章中的程序似乎表明malloc() 总是在于分配的内存量 - 即使禁用交换.例如,它高兴地"分配"3 GB的RAM,然后在程序开始实际写入所有内存时调用OOM杀手.在禁用交换的情况下,在写入仅有610 MB的3 GB malloc()可用空间后,将调用OOM杀手.

演示程序的目的是演示这个记录完备的Linux"功能",所以这一点都不太令人惊讶.但是我们基于i.MX6的嵌入式目标在工作中的行为是不同的,它malloc()似乎在说明它分配了多少RAM(?)下面的程序(从文章中逐字复制)总是会被OOM杀死第二个循环时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;
}
Run Code Online (Sandbox Code Playgroud)

所以我的问题,一言以蔽之,就是:为什么demo3程序或其他一些不吉利的OOM杀手被害人总是得到前不久杀i == n我的桌面系统(暗示malloc()是骗子),但它只能被杀死时,i == n我们的我. 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
Run Code Online (Sandbox Code Playgroud)

背景:我们正在尝试决定如何在面向媒体的嵌入式应用程序中处理低内存条件,并想知道我们是否可以 - 在分配失败时为此特定目标信任malloc()提醒我们.我对桌面Linux应用程序的体验让我觉得答案肯定不是,但现在我不太确定.

小智 8

一点背景

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

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

  2. 如果预分配的内存块中没有空间(记住,内存块通常非常小,大约128KB到1MB),它会要求内核提供更多内存.实际的系统调用从一个内核到另一个内核(mmap(),vm_allocate()...)各不相同,但其目的大致相同.

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

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

好了,现在你是程序成功malloc分配一些内存,但事实是,没有一个单一的页面(4KB的x86)的物理内存已实际分配给您的请求,但(当然,这是一个过于简单化,因为附带地,一些页面可能已被用于存储有关内存池状态的信息,但它使得更容易说明这一点).

那么,当您尝试访问最近的malloc内存时会发生什么?分段错误.出人意料的是,这是一个比较鲜为人知的事实,但你的系统产生分割故障所有的时间.然后中断您的程序,内核获得控制权,检查地址错误是否对应于有效的映射条目,获取一个或多个物理页面并将它们链接到任务的映射.

如果您的程序试图访问不在任务中的映射条目内的地址,则内核将无法解决该错误,并将向其指出的信号(或非UNIX系统的等效机制)发送这个问题.如果程序本身不处理该信号,它将被臭名昭着的Segmentation Fault错误杀死.

因此,当您调用时malloc(),不会分配物理内存,但实际访问该内存时.这允许操作系统做一些漂亮的技巧,如磁盘分页,滚动过度使用.

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

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

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

多少过度使用就足够了?

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

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

但为什么内存过量使用在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
Run Code Online (Sandbox Code Playgroud)

内核配置了2GB/2GB分割,因此系统的行为符合预期.

  • 从技术上讲,它不是分段错误,而是_page fault_导致实际内存被附加. (2认同)