发布于 2011-08-09 01:19:37
在您发布的图表中,-the地址是由brk操作的,sbrk-is是堆顶部的虚线。

您所阅读的文档将此描述为“数据段”的结束,因为在传统的(预共享库,预mmap) Unix中,数据段与堆是连续的;在程序启动之前,内核将将“文本”和“数据”块加载到从地址零开始的RAM中(实际上略高于地址零,因此空指针真正没有指向任何东西),并将中断地址设置为数据段的末尾。然后,对malloc的第一次调用将使用sbrk移动碎片,并在数据段顶部和新的、更高的中断地址之间创建堆,如图所示,随后使用malloc将使堆在必要时更大。
同时,堆栈从内存的顶部开始并向下扩展。堆栈不需要显式的系统调用来使其更大;要么它开始时分配给它的RAM尽可能多(这是传统的方法),要么堆栈下面有一个预留地址区域,当内核注意到试图写入它时,它会自动分配RAM (这是现代的方法)。无论哪种方式,在地址空间的底部都有一个可以用于堆栈的“保护”区域,也可能没有。如果存在此区域(所有现代系统都会这样做),则将永久取消映射;如果堆栈或堆试图发展为该区域,则会出现分段错误。但是,传统上内核并不试图强制执行边界;堆栈可以发展为堆,或者堆可以发展为堆栈,不管是哪种方式,它们都会在彼此的数据上乱写,程序就会崩溃。如果你很幸运的话,它就会立即坠毁。
我不知道这个图表中的512‘m是从哪里来的。它意味着一个64位的虚拟地址空间,这与您拥有的非常简单的内存映射不一致。一个真正的64位地址空间看起来更像这样:

Legend: t: text, d: data, b: BSS这不是远程扩展的,也不应该被解释为任何给定的操作系统都是如何工作的(在我绘制它之后,我发现Linux实际上使可执行文件的地址比我想象的更接近零,并且共享库的地址高得惊人)。这个图中的黑色区域没有映射--任何访问都会导致一个直接的分段故障--相对于灰色区域,它们是巨大的。浅灰区域是程序及其共享库(可以有几十个共享库);每个库都有一个独立的文本和数据段(和"bss“段,其中也包含全局数据,但被初始化为所有位-零,而不是占用磁盘上可执行文件或库中的空间)。堆不再一定要连续使用可执行文件的数据段--我是这样画的,但是看起来至少Linux不这样做。堆栈不再固定在虚拟地址空间的顶部,堆与堆栈之间的距离非常大,因此不必担心跨越它。
断点仍然是堆的上限。然而,我没有显示的是,可能有几十个独立的内存分配在那里的某个地方,用mmap而不是brk制作的。(操作系统将试图将这些远离brk区域,这样它们就不会发生碰撞。)
发布于 2015-06-26 21:23:28
最小可运行示例
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;
}即使没有brk,上面可能也不会触及一个新页面,而不是分段错误,因此这里有一个更激进的版本,它分配16 hit,并且很可能在没有brk的情况下进行分段错误。
#define _GNU_SOURCE
#include <assert.h>
#include <unistd.h>
int main(void) {
void *b;
char *p, *end;
b = sbrk(0);
p = (char *)b;
end = p + 0x1000000;
brk(end);
while (p < end) {
*(p++) = 1;
}
brk(b);
return 0;
}在Ubuntu 18.04上测试。
虚拟地址空间可视化
在brk之前
+------+ <-- Heap Start == Heap Endbrk(p + 2)后
+------+ <-- Heap Start + 2 * sizof(int) == Heap End
| |
| You can now write your ints
| in this memory area.
| |
+------+ <-- Heap Startbrk(b)后
+------+ <-- Heap Start == Heap End为了更好地理解地址空间,您应该熟悉分页:x86分页是如何工作的?。
为什么我们同时需要brk 和 sbrk**?**?
当然,brk可以用sbrk +偏移量计算来实现,两者都是为了方便而存在的。
在后端,Linux内核v5.0有一个单独的系统调用brk,用于实现这两者:64.tbl#L23
12 common brk __x64_sys_brkbrk 是 POSIX?
brk过去是POSIX,但它在POSIX 2001中被删除,因此_GNU_SOURCE需要访问glibc包装器。
删除可能是由于引入了mmap,它是一个超集,允许分配多个范围和更多的分配选项。
我认为,现在您应该使用brk而不是malloc或mmap,这是没有道理的。
brk 诉 malloc
brk是实现malloc的一种老可能性。
mmap是一种新的、功能更强大的机制,很可能所有POSIX系统目前都使用它来实现malloc。这是一个内存分配示例。
我能把brk 和malloc混在一起吗?
如果您的malloc是用brk实现的,我不知道这怎么可能不会炸掉东西,因为brk只管理一个内存范围。
然而,我在glibc文档中找不到任何关于它的信息,例如:
我认为,由于mmap可能用于malloc,所以可能只在那里工作。
另请参阅:
更多信息
在内部,内核决定进程是否可以拥有那么多内存,并为这种使用指定内存页。
这说明了堆栈与堆:在x86程序集中的寄存器上使用的push / pop指令的功能是什么?的比较。
发布于 2011-08-09 06:10:49
您可以自己使用brk和sbrk来避免每个人总是抱怨的"malloc开销“。但是,您不能轻易地在malloc中使用此方法,因此只有在不必使用free的情况下才适合使用该方法。而且,您应该避免任何可能在内部使用malloc的库调用。即。strlen可能是安全的,但fopen可能不安全。
打电话给sbrk,就像你打给malloc一样。它返回一个指向当前中断的指针,并将该值增加一个值。
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个字符。
https://stackoverflow.com/questions/6988487
复制相似问题