brk()系统调用是做什么的?

根据Linux程序员手册:

brk()和sbrk()改变程序中断的位置,它定义了进程数据段的结束。

数据段在这里意味着什么? 它只是数据段或数据,BSS和堆的组合?

根据维基:

有时数据,BSS和堆区被统称为“数据段”。

我没有理由改变数据段的大小。 如果是数据,BSS和堆集体然后它是有道理的,因为堆将获得更多的空间。

这使我想到了第二个问题。 在我读到的所有文章中,作者说堆积向上,堆积向下。 但是他们没有解释的是,当堆占用堆栈之间的所有空间时会发生什么?

在这里输入图像说明

我看到很多部分的答案,但没有完整的答案。 这是你再次发布的图片:

虚拟内存布局的简化图像

“break”(由brksbrk操作的地址)是顶部的虚线。 你读过的文档把这个描述为“数据段”的末尾,因为在传统的(pre-shared-libraries,pre- mmap )Unix中,数据段与堆是连续的; 在程序启动之前,内核会将“text”和“data”块从地址0(实际上是地址0以上的一点,所以NULL指针真的没有指向任何东西)加载到RAM中,并将中断地址设置为数据段的结尾。 对malloc的第一次调用将使用sbrk来移动分割,并在数据段顶部和新的更高的中断地址之间创build堆,如图所示,随后使用malloc将使用它来创build堆是必要的。

同时,堆栈从内存的顶部开始并逐渐减小。 堆栈不需要显式的系统调用来使其变大; 要么就开始分配尽可能多的RAM(这是传统的方法),要么在堆栈下面有一个保留地址的区域,当内核注意到在那里写入数据时,内核会自动分配RAM (这是现代的方法)。 无论哪种方式,在可用于堆栈的地址空间底部可能有也可能不存在“保护”区域。 如果这个区域存在(所有的现代系统都这样做),它永远不会被映射; 如果堆栈或堆试图成长,就会出现分段错误。 然而,传统上,内核并没有尝试强制实施边界。 堆栈可能变成堆栈,或者堆栈可能堆积起来,无论如何,它们会在对方的数据上乱涂乱画,程序就会崩溃。 如果你非常幸运,它会立即崩溃。

我不确定这个图中512GB的数字来自哪里。 这意味着一个64位的虚拟地址空间,这与您在那里的非常简单的内存映射不一致。 一个真正的64位地址空间看起来更像这样:

简化的地址空间

这不是远程扩展,它不应该被解释为任何给定的操作系统是如何做的(在我绘制它之后,我发现Linux实际上使得可执行文件比我想象的更接近地址零,而共享库在令人惊讶的高地址)。 该图的黑色区域未映射 – 任何访问都会导致立即发生段错误 – 而且相对于灰色区域,它们是巨大的 。 浅灰色区域是程序及其共享库(可以有数十个共享库); 每个都有独立的文本和数据段(和“bss”段,它也包含全局数据,但被初始化为全零位,而不是占用磁盘上可执行文件或库中的空间)。 堆不再一定是可执行文件的数据段 – 我是这样画的,但是看起来像Linux至less不会这样做。 堆栈不再固定在虚拟地址空间的顶部,堆栈和堆栈之间的距离非常巨大,您不必担心会越过堆栈。

rest仍然是堆的上限。 然而,我没有显示的是,在黑色的地方,可能会有数十个独立的内存分配,使用mmap而不是brk 。 (操作系统会尽量远离brk区域,以免碰撞。)

最小的可运行示例

brk()系统调用是做什么的?

要求内核让你读取和写入连续的称为堆的内存块。

如果你不问,它可能会段错误的你。

没有brk

 #define _GNU_SOURCE #include <unistd.h> int main(void) { /* Get the first address beyond the end of the heap. */ void *b = sbrk(0); int *p = (int *)b; /* May segfault because it is outside of the heap. */ *p = 1; return 0; } 

brk

 #define _GNU_SOURCE #include <assert.h> #include <unistd.h> int main(void) { void *b = sbrk(0); int *p = (int *)b; /* Move it 2 ints forward */ brk(p + 2); /* Use the ints. */ *p = 1; *(p + 1) = 2; assert(*p == 1); assert(*(p + 1) == 2); /* Deallocate back. */ brk(b); return 0; } 

在Ubuntu 14.04上testing

虚拟地址空间可视化

brk之前:

 +------+ <-- Heap Start == Heap End 

brk(p + 2)

 +------+ <-- Heap Start + 2 * sizof(int) == Heap End | | | You can now write your ints | in this memory area. | | +------+ <-- Heap Start 

brk(b)

 +------+ <-- Heap Start == Heap End 

为了更好地理解地址空间,你应该让自己熟悉分页: x86分页是如何工作的?

更多信息

brk是POSIX,但在POSIX 2001中被删除,因此需要_GNU_SOURCE来访问glibc包装。

删除可能是由于引入mmap ,这是一个超集,允许多个范围分配和更多的分配选项。

在内部,内核决定进程是否可以拥有那么多的内存,并为此使用内存页面 。

brkmmap是libc用于在POSIX系统中实现malloc的常见底层机制。

这解释了堆栈如何与堆进行比较: 在x86程序集的寄存器中使用的push / pop指令的function是什么?

你可以使用brksbrk来避免每个人都在抱怨的“malloc开销”。 但是你不能轻易地将这个方法与malloc结合使用,所以只有在你不需要free任何东西时才适用。 因为你不能。 此外,您应该避免任何可能会在内部使用malloc库调用。 IE浏览器。 strlen大概是安全的,但是fopen可能不是。

像调用malloc一样调用sbrk 。 它返回一个指向当前中断的指针并以此数量递增中断。

 void *myallocate(int n){ return sbrk(n); } 

虽然你不能释放单独的分配(因为没有malloc开销 ,记住),你可以释放整个空间通过调用brk与第一次调用sbrk返回的值,从而倒带brk

 void *memorypool; void initmemorypool(void){ memorypool = sbrk(0); } void resetmemorypool(void){ brk(memorypool); } 

你甚至可以把这些地区叠加起来,通过倒退到这个地区的开始,放弃最近的地区。


还有一件事 …

sbrk在代码高尔夫中也是有用的,因为它比malloc短2个字符。

有一个特殊的指定的匿名私有内存映射(传统上位于data / bss以外,但现代Linux实际上将调整与ASLR的位置)。 原则上它不会比任何可以用mmap创build的其他映射更好,但是Linux有一些优化,使得可以向上扩展这个映射的结束(使用brk系统调用),相对于mmapmremap会产生的locking成本降低。 这使得在实现主堆时使用malloc实现很有吸引力。

我可以回答你的第二个问题。 Malloc将失败并返回一个空指针。 这就是为什么在dynamic分配内存时总是检查空指针的原因。

堆放在程序的数据段中。 brk()用于更改(扩展)堆的大小。 当堆不能再增长时,任何malloc调用都将失败。

数据段是存储所有静态数据的内存部分,在启动时从可执行文件读入,通常是零填充的。

malloc使用brk系统调用来分配内存。

包括

 int main(void){ char *a = malloc(10); return 0; } 

用strace运行这个简单的程序,它会调用brk系统。

  1. 处理内存分配的系统调用是sbrk(2) 。 它将进程的地址空间增加或减less指定的字节数。

  2. 内存分配函数malloc(3)实现了一种特定types的分配。 malloc()函数,它可能会使用sbrk()系统调用。

内核中的系统调用sbrk(2)代表进程分配额外的空间块。 malloc()库函数从用户级别pipe理这个空间。