c代码:
// program break mechanism
// TLPI exercise 7-1
#include <stdio.h>
#include <stdlib.h>
void program_break_test() {
printf("%10p\n", sbrk(0));
char *bl = malloc(1024 * 1024);
printf("%x\n", sbrk(0));
free(bl);
printf("%x\n", sbrk(0));
}
int main(int argc, char **argv) {
program_break_test();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编译以下代码时:
printf("%10p\n", sbrk(0));
Run Code Online (Sandbox Code Playgroud)
我收到警告提示:
format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘int’
问题1:为什么?
在我之后malloc(1024 * 1024),程序突破似乎没有改变.
这是输出:
9b12000
9b12000
9b12000
Run Code Online (Sandbox Code Playgroud)
问题2:进程在启动以备将来使用时是否在堆上分配内存?或者编译器改变分配的时间点?否则,为什么?
[更新]摘要:brk()或mmap()
在查看TLPI并检查手册页(在TLPI的作者的帮助下)之后,现在我了解了如何malloc()决定使用brk()或mmap(),如下所示:
mallopt() …
malloc使用brk/ sbrk作为从OS声明内存的主要方式的典型实现。但是,它们还用于mmap获取大容量分配的块。使用brk代替确实有真正的好处mmap,还是仅仅是传统?将其与所有内容一起使用mmap是否会很好?
(注意:我在这里可以互换使用sbrk,brk因为它们是同一个Linux系统调用接口brk。)
作为参考,以下是一些描述glibc malloc的文档:
GNU C库参考手册:GNU分配器
https://www.gnu.org/software/libc/manual/html_node/The-GNU-Allocator.html
glibc Wiki:Malloc概述
https://sourceware.org/glibc/wiki/MallocInternals
这些文件所描述的是,它sbrk被用来声明一个小的分配的主要场所,mmap被用来声明一个次级的场所,mmap还被用来声明一个大对象(“比页面大得多”)的空间。
同时使用应用程序堆(带有sbrk),并mmap引入了一些其他不必要的复杂性:
分配的竞技场-主竞技场使用应用程序的堆。其他竞技场使用mmap堆。要将块映射到堆,您需要知道哪种情况适用。如果该位为0,则该块来自主区域和主堆。如果该位为1,则该块来自mmap的内存,并且可以从该块的地址计算出堆的位置。
[Glibc malloc源自ptmalloc,而ptmalloc则源自dlmalloc,后者始于1987年。]
该jemalloc手册页(http://jemalloc.net/jemalloc.3.html)有这样一段话:
传统上,分配器使用sbrk(2)获取内存,由于一些原因,该内存不是最佳的,原因包括竞争条件,增加的碎片以及人为限制最大可用内存。如果操作系统支持sbrk(2),则此分配器将按优先顺序使用mmap(2)和sbrk(2);否则,此分配器将使用mmap(2)和sbrk(2)。否则,仅使用mmap(2)。
因此,他们甚至在这里说这sbrk不是次优的,但是无论如何他们还是会使用它,即使他们已经为编写代码而烦恼,以至于没有它就可以工作。
[jemalloc的编写始于2005年。]
更新:更多地考虑这一点,关于“按优先顺序”的一点让我对询问保持了一致。为什么选择优先顺序?它们是否只是sbrk在mmap不支持(或缺少必要功能)的情况下用作备用,还是该进程可能进入可以使用sbrk但不能使用的状态mmap?我看一下他们的代码,看看是否能弄清楚它在做什么。
我之所以问是因为我正在用C实现垃圾回收系统,到目前为止,我看不到除之外的任何用途mmap。我想知道是否还有什么我想念的。
(就我而言,我还有另一个避免使用的原因brk,那就是malloc在某些时候可能需要使用。)
自从我被介绍到 以来C,我就被告知C动态内存分配是使用家族中的函数完成的malloc。我还了解到,动态分配的内存malloc是在进程的堆部分分配的。
各种操作系统教科书都说这malloc涉及系统调用(虽然并不总是但有时)来将堆上的结构分配给进程。现在假设malloc返回指向堆上分配的字节块的指针,为什么它需要系统调用。函数的激活记录放置在进程的堆栈部分中,并且由于“堆栈部分”已经是进程虚拟地址空间的一部分,所以激活记录的压入和弹出、堆栈指针的操作,只需从虚拟地址空间的最高可能地址。它甚至不需要系统调用。
现在基于同样的理由,由于“堆部分”也是进程虚拟地址空间的一部分,为什么需要系统调用来在该部分中分配字节块。像这样的例程malloc可以自行处理“空闲”列表和“已分配”列表。它需要知道的只是“数据部分”的结尾。某些文本说系统调用对于“将内存附加到进程以进行动态内存分配”是必要的,但是如果malloc在“堆部分”上分配内存,为什么需要在 期间将内存附加到进程malloc?可以简单地取自过程中已经一部分的部分。
在阅读 Kernighan 和 Ritchie 的《C 编程语言》[2e] 文本时,我发现了他们对该malloc函数的实现 [第 8.7 节第 185-189 页]。作者说:
malloc根据需要调用操作系统获取更多内存。
这就是操作系统文本所说的,但与我上面的想法相反(如果malloc在堆上分配空间)。
由于向系统请求内存是一项相对昂贵的操作,因此作者不会在每次调用 时都这样做,因此他们创建了一个至少请求单位的malloc函数;这个较大的块根据需要被切碎。而基本的空闲列表管理是由.morecoreNALLOCfree
但问题是作者使用sbrk()向操作系统请求内存morecore。现在维基百科说:
brk和是 Unix 和类 Unix 操作系统中使用的基本内存管理系统调用,用于控制分配给进程数据段sbrk的内存量。
在哪里
数据段(通常表示为.data)是目标文件或程序的相应地址空间的一部分,其中包含初始化的静态变量,即全局变量和静态局部变量。
我猜这不是“堆部分”。[数据部分是上图中从下数第二部分,而堆是从下数第三部分。]
我完全困惑了。我想知道到底发生了什么以及这两个概念如何正确?请通过将分散的碎片连接在一起来帮助我理解这个概念......