【问题标题】:Does malloc lazily create the backing pages for an allocation on Linux (and other platforms)?malloc 是否懒惰地为 Linux(和其他平台)上的分配创建支持页面?
【发布时间】:2010-10-29 01:14:54
【问题描述】:

如果我去malloc(1024 * 1024 * 1024) 在 Linux 上,malloc 实际上是做什么的?

我确定它为分配分配了一个虚拟地址(通过遍历空闲列表并在必要时创建一个新映射),但它实际上会创建 1 GiB 的交换页吗?还是mprotect 地址范围并在您实际触摸它们时创建页面,就像mmap 一样?

(我指定 Linux 是因为 the standard 对这些细节保持沉默,但我很想知道其他平台也做什么。)

【问题讨论】:

  • 有趣的问题;我也会对其他平台上的行为感到好奇,但是将这个问题锁定在 Linux 上是值得称赞的。
  • 有一段时间,这似乎是很多内存......

标签: linux malloc


【解决方案1】:

Linux 会延迟页面分配,也就是。 '乐观的内存分配'。从 malloc 返回的内存没有任何支持,当您触摸它时,您实际上可能会遇到 OOM 条件(如果您请求的页面没有交换空间),在这种情况下 a process is unceremoniously terminated

例如见http://www.linuxdevcenter.com/pub/a/linux/2006/11/30/linux-out-of-memory.html

【讨论】:

  • 看看内核如何计算进程的“坏度”以确定在内存不足时要杀死哪个进程是很有趣的。
  • IIRC 它有层级:从最高到最低 - 根进程、执行 I/O 的进程、休眠进程……最低的得到子弹。
  • @Aiden 链接中描述了用于确定要杀死哪个进程的“badness”函数。
  • 晚期OOM行为并不总是正确的;这取决于过度使用设置。三种模式见kernel.org/doc/Documentation/vm/overcommit-accounting
【解决方案2】:

9. Memory(Andries Brouwer The Linux kernel, Some remarks on the Linux Kernel 的一部分)是一个很好的文档。

它包含以下程序,这些程序演示了 Linux 处理物理内存与实际内存的对比,并解释了内核的内部结构。

通常,第一个演示程序会在 malloc() 返回 NULL 之前获得大量内存。第二个演示程序将获得更少量的内存,因为之前获得的内存实际上已被使用。第三个程序会得到和第一个程序一样大的内存量,然后当它想使用它的内存时被杀死。

演示程序1:分配内存而不使用它。

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

int main (void) {
    int n = 0;

    while (1) {
        if (malloc(1<<20) == NULL) {
                printf("malloc failure after %d MiB\n", n);
                return 0;
        }
        printf ("got %d MiB\n", ++n);
    }
}

演示程序2:分配内存并实际触摸它。

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

int main (void) {
    int n = 0;
    char *p;

    while (1) {
        if ((p = malloc(1<<20)) == NULL) {
                printf("malloc failure after %d MiB\n", n);
                return 0;
        }
        memset (p, 0, (1<<20));
        printf ("got %d MiB\n", ++n);
    }
}

演示程序3:先分配,后使用。

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

(在运行良好的系统上,例如Solaris,三个演示程序获得相同数量的内存并且不会崩溃,但请参见 malloc() 返回 NULL。)

【讨论】:

  • “运行良好”是一个见仁见智的问题。事实上,Linux 在 /proc/sys/vm 中有选项来控制过度使用行为。如果你愿意,你可以像 Solaris 一样拥有它。
  • 请注意,/proc/sys/vm 大部分时间都已损坏! groups.google.com/group/comp.os.linux.development.system/… 这是一个关于 LINUX 和磁盘性能的好提示。如果您曾经做过大副本,并且大量缓存被用完,并且您的 I/O 系统开始陷入困境.... echo 1 > /proc/sys/vm/drop_caches 然后 presto-chango 备份到高吞吐量 :) 去看看!!
【解决方案3】:

我在同一主题的类似帖子中给出了这个答案:

Are some allocators lazy?

这开始有点偏离主题(然后我会将它与您的问题联系起来),但是发生的事情类似于您在 Linux 中分叉一个进程时发生的事情。分叉时有一种称为写时复制的机制,它只在内存也被写入时为新进程复制内存空间。这样,如果分叉的进程 exec 立即成为一个新程序,那么您就节省了复制原始程序内存的开销。

回到你的问题,这个想法是相似的。正如其他人指出的那样,请求内存会立即为您提供虚拟内存空间,但实际页面仅在写入时分配。

这样做的目的是什么?它基本上使 mallocing 内存成为或多或少的恒定时间操作 Big O(1) 而不是 Big O(n) 操作(类似于 Linux 调度程序传播它的方式而不是在一大块中进行)。

为了证明我的意思,我做了以下实验:

rbarnes@rbarnes-desktop:~/test_code$ time ./bigmalloc

real    0m0.005s
user    0m0.000s
sys 0m0.004s
rbarnes@rbarnes-desktop:~/test_code$ time ./deadbeef

real    0m0.558s
user    0m0.000s
sys 0m0.492s
rbarnes@rbarnes-desktop:~/test_code$ time ./justwrites

real    0m0.006s
user    0m0.000s
sys 0m0.008s

bigmalloc 程序分配了 2000 万个整数,但对它们不做任何事情。 deadbeef 将一个 int 写入每个页面,导致 19531 次写入,而 justwrites 分配 19531 个 int 并将它们清零。如您所见, deadbeef 的执行时间比 bigmalloc 长约 100 倍,比 justwrites 长约 50 倍。

#include <stdlib.h>

int main(int argc, char **argv) {

    int *big = malloc(sizeof(int)*20000000); // Allocate 80 million bytes

    return 0;
}

.

#include <stdlib.h>

int main(int argc, char **argv) {

    int *big = malloc(sizeof(int)*20000000); // Allocate 80 million bytes

    // Immediately write to each page to simulate an all-at-once allocation
    // assuming 4k page size on a 32-bit machine.

    for (int* end = big + 20000000; big < end; big += 1024)
        *big = 0xDEADBEEF;

    return 0;
}

.

#include <stdlib.h>

int main(int argc, char **argv) {

    int *big = calloc(sizeof(int), 19531); // Number of writes

    return 0;
}

【讨论】:

    【解决方案4】:

    Malloc 从 libc 管理的块中分配内存。当需要额外的内存时,库使用 brk 系统调用进入内核。

    内核将虚拟内存页面分配给调用进程。这些页面作为进程拥有的资源的一部分进行管理。当内存被 brk'd 时,不分配物理页。当进程访问 brk'd 页面之一中的任何内存位置时,就会发生页面错误。内核验证虚拟内存是否已分配并继续将物理页面映射到虚拟页面。

    页面分配不仅限于写入,而且与写入时复制完全不同。任何访问(读取或写入)都会导致页面错误和物理页面的映射。

    请注意,堆栈内存是自动映射的。也就是说,不需要显式的 brk 将页面映射到堆栈使用的虚拟内存。

    【讨论】:

    【解决方案5】:

    在 Windows 上,页面已提交(即可用的可用内存减少),但在您触摸页面(读取或写入)之前不会实际分配它们。

    【讨论】:

      【解决方案6】:

      在大多数类 Unix 系统上,它管理 brk 边界。 VM 在处理器命中时添加页面。至少 Linux 和 BSDs 会这样做。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-17
        • 1970-01-01
        • 1970-01-01
        • 2012-06-02
        • 2021-01-08
        • 1970-01-01
        • 2017-01-13
        相关资源
        最近更新 更多