malloc是否懒惰地创buildLinux(和其他平台)上的分配的支持页面?
在Linux上,如果我是malloc(1024 * 1024 * 1024)
,malloc实际上做了什么?
我确定它分配一个虚拟地址的分配(通过走空闲列表和创build一个新的映射,如果有必要),但它实际上创造了1吉比特价值的交换页面? 还是它mprotect
地址范围,并创build页面,当你实际触摸他们像mmap
呢?
(我指定了Linux,因为标准对这些细节没有提及,但是我很想知道其他平台也是如此。)
Linux确实推迟了页面分配。 “乐观的内存分配”。 你从malloc中获得的内存不会被任何东西所支持,当你触摸它的时候,你实际上可能会得到一个OOM条件(如果没有你请求的页面的交换空间),在这种情况下, 一个进程会被毫不客气地终止 。
看例如http://www.linuxdevcenter.com/pub/a/linux/2006/11/30/linux-out-of-memory.html
9.内存 ( Linux内核的一部分,Andries Brouwer 在Linux内核上的一些评论 )是一个很好的文档。
它包含以下程序,演示Linux处理物理内存与实际内存的关系,并解释内核的内部结构。
通常,第一个演示程序将在malloc()返回NULL之前获得非常大量的内存。 第二个演示程序将获得更less的内存,现在已经使用了较早获得的内存。 第三个程序会得到和第一个程序一样大的数量,然后当它想要使用它的内存时就会被杀死。
演示程序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这样的function良好的系统上,三个演示程序获得的内存量相同,不会崩溃,但请参阅malloc()返回NULL。)
我在同一主题上给了这个答复一个类似的post:
有些分配器是懒惰的吗?
这开始了一个小题目(然后我会把它与你的问题),但是发生什么类似于当你在Linux中分叉进程时发生的事情。 分叉时,有一种称为“拷贝写入”的机制,它只在内存写入时才复制新进程的内存空间。 这样,如果分叉的进程exec立即执行一个新的程序,那么你已经节省了复制原始程序内存的开销。
回到你的问题,这个想法是相似的。 正如其他人已经指出的,请求内存立即获得虚拟内存空间,但实际页面只在写入时才分配。
这是什么目的? 它基本上使内存mallocing一个或多或less的恒定时间操作Big O(1),而不是一个大的O(N)操作(类似于Linux调度程序扩展它的工作,而不是在一个大块)。
为了certificate我的意思,我做了以下的实验:
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写入一个整数到每个页面导致19531写入和justwrites分配19531整数和零。 正如你所看到的,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; }
malloc从libcpipe理的块中分配内存。 当需要额外的内存时,库使用brk系统调用进入内核。
内核为调用进程分配虚拟内存页面。 这些页面作为进程所拥有的资源的一部分进行pipe理。 内存不足时物理页面不分配。 当进程访问其中一个页面的任何内存位置时,会发生页面错误。 内核validation虚拟内存已分配,并继续将物理页面映射到虚拟页面。
页面分配不限于写入,与写入时的复制完全不同。 任何访问(读取或写入)都会导致页面错误并映射物理页面。
请注意,堆栈内存是自动映射的。 也就是说,显式brk不需要将页面映射到堆栈使用的虚拟内存。
在Windows上,页面被提交(即可用内存可用),但在触摸页面(读取或写入)之前,实际上不会分配页面。
在大多数类Unix系统上,它pipe理着brk的边界。 虚拟机在被处理器命中时添加页面。 至lessLinux和BSD是这样做的。