brk()系统调用是做什么的?
根据Linux程序员手册:
brk()和sbrk()更改程序中断的位置,该中断定义了进程数据段的结尾。
数据段在这里意味着什么? 它只是数据段或数据,BSS和堆的组合?
根据维基:
有时数据,BSS和堆区统称为“数据段”。
我没有理由改变数据段的大小。 如果它是数据,BSS和堆集体然后它是有道理的,因为堆将获得更多的空间。
这让我想到了第二个问题。 在我读到的所有文章中,作者说,堆向上增长,堆栈向下增长。 但是他们没有解释的是,当堆占用堆和栈之间的所有空间时会发生什么?
我看到很多部分的答案,但没有完整的答案。 这是您再次发布的图片:
“break” - 由brk
和sbrk
操作的地址 - 是堆顶部的虚线。 您读过的文档将此描述为“数据段”的末尾,因为在传统(预共享库,预mmap
)Unix中,数据段与堆是连续的; 在程序启动之前,内核会将“文本”和“数据”块从地址0(实际上高于地址0,因此NULL指针真正没有指向任何东西)加载到RAM中,并将中断地址设置为数据段的结尾。 第一次调用malloc
会使用sbrk
来移动分隔sbrk
,并在数据段顶部和新的更高断点地址之间创建堆,如图所示,随后使用malloc
将使用它来创建堆是必要的。
同时,堆栈从内存顶部开始并逐渐减小。 堆栈不需要明确的系统调用来使其更大; 要么开始分配尽可能多的RAM(这是传统的方法),要么在堆栈下面有一个保留地址区域,当它发现尝试在那里写入内容时,内核会自动为其分配RAM (这是现代的方法)。 无论哪种方式,地址空间底部都可能有或没有可用于堆栈的“保护”区域。 如果这个区域存在(所有现代系统都这样做),它永远不会被映射; 如果堆栈或堆试图进入它,你会得到一个分段错误。 然而,传统上,内核并未尝试强制实施边界; 堆栈可能会堆积到堆中,或者堆可能堆积到堆栈中,并且无论如何他们会在彼此的数据上涂写,程序会崩溃。 如果你非常幸运,它会立即崩溃。
我不确定这个图中512GB的数字来自哪里。 它意味着一个64位的虚拟地址空间,这与您在那里的非常简单的内存映射不一致。 一个真正的64位地址空间看起来更像这样:
这不是可以远程扩展的,它不应该被解释为给定操作系统如何操作(在我绘制它之后),我发现Linux实际上使可执行文件更接近于地址零,比我想象的要更接近于共享库在令人惊讶的高地址)。 该图的黑色区域未映射 - 任何访问都会导致立即发生段错误 - 并且它们相对于灰色区域是巨大的。 浅灰色区域是程序及其共享库(可以有数十个共享库); 每个都有一个独立的文本和数据段(和“bss”段,它也包含全局数据,但被初始化为全零位,而不是占用磁盘上可执行文件或库中的空间)。 堆不再一定要连续执行可执行文件的数据段 - 我以这种方式绘制了它,但看起来像Linux至少不会这样做。 堆栈不再与虚拟地址空间的顶端相连,堆栈和堆栈之间的距离非常大,您不必担心会越过堆栈。
休息仍然是堆的上限。 然而,我没有说明的是,在黑色的地方,可能会有数十个独立的内存分配,使用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上测试过。
虚拟地址空间可视化
在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
,它是一个允许分配多个范围和更多分配选项的超集。
在内部,内核决定进程是否可以拥有那么多的内存,并为此使用内存页面。
brk
和mmap
是libc用于在POSIX系统中实现malloc
的常见底层机制。
这解释了堆栈如何与堆进行比较:在x86程序集的寄存器中使用的push / pop指令的功能是什么?
你可以使用brk
和sbrk
来避免每个人都在抱怨的“malloc开销”。 但是你不能轻易地将这个方法与malloc
结合使用,所以只有在你不需要free
任何东西时才适用。 因为你不能。 此外,你应该避免任何可能在内部使用malloc
库调用。 IE浏览器。 strlen
可能是安全的,但fopen
可能不是。
像调用malloc
一样调用sbrk
。 它返回一个指向当前中断的指针并按该数量递增中断。
void *myallocate(int n){
return sbrk(n);
}
虽然不能释放单个分配(因为没有malloc开销,请记住),但可以通过首先调用sbrk
返回的值调用brk
来释放整个空间,从而倒回brk。
void *memorypool;
void initmemorypool(void){
memorypool = sbrk(0);
}
void resetmemorypool(void){
brk(memorypool);
}
你甚至可以叠加这些区域,通过将中断倒退到该区域的开始来丢弃最近的区域。
还有一件事 ...
sbrk
在代码高尔夫中也很有用,因为它比malloc
短2个字符。