brk
和sbrk
操纵的地址——是堆顶部的虚线。
您所阅读的文档将其描述为“数据段”的末尾,因为在传统(预共享库,预)Unix中,数据段与堆是连续的;在程序启动之前,内核会将“文本”和“数据”块加载到RAM中,从地址零开始(实际上略高于地址零,以便NULL指针确实不指向任何东西),并将断点地址设置为数据段的末尾。然后,第一次调用将使用将断点向上移动,并创建堆,位于数据段顶部和新的更高断点地址之间,如图所示,并且随后对malloc
的使用将使用它将堆扩大到必要的大小。 Legend: t: text, d: data, b: BSS
malloc
是否仍然依赖于brk
,还是使用mmap
来能够“归还”单独的内存块? - Anders Abelmalloc
函数对于小型分配使用 brk
区域,而对于大型分配(例如 >128K)则使用单独的 mmap
。例如,在 Linux 的 malloc(3)
手册中可以看到关于 MMAP_THRESHOLD 的讨论。 - zwolmmap
提供规则;它非常依赖于操作系统。 - zwol#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
,上述代码可能不会打开新页面,也不会发生段错误。因此,这里有一个更激进的版本,它分配了 16MiB 的内存,并且很有可能在没有 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;
}
+------+ <-- 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
+------+ <-- Heap Start == Heap End
brk
和sbrk
?brk
可以通过sbrk
+ 偏移计算来实现,两者都是为了方便而存在。brk
,用于实现这两者:https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64.tbl#L23。12 common brk __x64_sys_brk
brk
是POSIX吗?
brk
曾经是POSIX的一部分,但在POSIX 2001中被移除,因此需要使用_GNU_SOURCE
来访问glibc的包装器。mmap
,它是一个更强大的超集,允许分配多个范围和更多的分配选项。brk
而不是malloc
或mmap
。
brk
与malloc
brk
是一种旧的实现malloc
的可能性。
mmap
是一种更新、更强大的机制,很可能所有的POSIX系统都使用它来实现malloc
。这里有一个最小可运行的mmap
内存分配示例。
我可以混合使用brk
和malloc
吗?
如果你的malloc
是使用brk
实现的,我不知道这样做怎么可能不会出问题,因为brk
只能管理单个内存范围。
然而,我在glibc文档中没有找到任何相关信息,例如:
事情在那里可能只是工作,我猜测是因为可能使用了mmap来进行malloc。p
是指向int
类型的指针,所以这句话应该改为brk(p + 2);
? - Johan Boulé*(p + i) = 1;
。 - lima.sierrabrk
系统调用)。 brk
稍微更方便一些,可以恢复先前分配的堆栈。 - Ciro Santilli OurBigBook.comassert
-- 使用 if
和 printf
可能会更好。 - S.S. Annebrk
和sbrk
来避免大家经常抱怨的"malloc开销",但是当你需要使用malloc
时,你就不能轻易地采用这种方法。所以,只适用于不需要free
任何东西的情况下。因为你无法释放它们。此外,你应该避免使用可能在内部使用malloc
的库调用。例如,strlen
可能是安全的,但fopen
可能不是。
像使用malloc
一样调用sbrk
。它返回当前断点的指针并增加那个量的断点。
void *myallocate(int n){
return sbrk(n);
}
虽然您不能释放单个分配(因为没有 malloc-overhead ,请记住),但是您可以通过使用第一次调用sbrk
返回的值调用 brk
来释放整个空间,从而倒回 brk。
void *memorypool;
void initmemorypool(void){
memorypool = sbrk(0);
}
void resetmemorypool(void){
brk(memorypool);
}
您甚至可以堆叠这些区域,通过将断点回退到该区域的开头来丢弃最近的区域。
还有一件事...
sbrk
在代码高尔夫比赛中也很有用,因为它比malloc
短2个字符。
malloc
/free
几乎肯定可以(并且确实会)将内存返还给操作系统。它们可能不总是在您希望它们这样做的时候这样做,但这是启发式算法对您的用例调整不完美的问题。更重要的是,在任何可能调用malloc
的程序中使用非零参数调用sbrk
是不安全的——几乎所有的C库函数都被允许在内部调用malloc
,唯一绝不会这样做的是异步信号安全函数。 - zwolmalloc
的风险。 - luser droogsbrk
仅在代码高尔夫方面有用,因为手动使用mmap(MAP_ANONYMOUS)
方式在除源代码大小之外的所有方面都更优。 - Peter Cordes有一个特殊的指定匿名私有内存映射(传统上位于数据/ bss之后,但现代Linux实际上会通过ASLR调整位置)。原则上,它与您使用mmap
创建的任何其他映射一样,但是Linux具有一些优化功能,使得可以使用brk
系统调用向上扩展此映射的末尾,相对于mmap
或mremap
会产生较少的锁定成本。这使得在实现主堆时,malloc
实现使用它非常有吸引力。
malloc函数使用brk系统调用来分配内存。
#include
int main(void){
char *a = malloc(10);
return 0;
}
使用strace运行这个简单的程序,它将调用brk系统。
brk()
来改变(扩展)堆的大小。当堆无法再增长时,任何malloc
调用都将失败。malloc()
在底层会使用 brk()
和/或 sbrk()
,如果你想实现自己定制版本的 malloc()
,也可以这样做。 - Daniel Pryden.bss
)在程序启动之前由操作系统初始化为全零比特;这实际上是由C标准保证的。我想有些嵌入式系统可能不会费心进行此操作(我从未见过这样的情况,但我并不是完全从事嵌入式)。 - zwolmmap
返回的页面清零,但我认为.bss
仍然会被清零。BSS空间可能是表达程序需要一些零数组的最紧凑方式。 - Peter Cordes.bss
中且不清零.bss
的C实现因此是不符合标准的。但是没有任何强制要求C实现必须使用.bss
或者拥有这样的东西。 - zwolmain
之前运行;该代码可以清零 .bss
区域而不是让内核执行,这仍然是符合规范的。 - zwol
brk()
系统调用在汇编语言中比在 C 语言中更有用。在 C 语言中,为了分配数据,应该使用malloc()
而不是brk()
-- 但这并不会以任何方式否定所提出的问题。 - alecovbrk()
和sbrk()
会操作任何“堆栈”?堆栈是由页面分配器在更低的层次上管理的。 - Ben Voigt