nik*_*nik 173 c unix linux memory-management brk
根据Linux程序员手册:
brk()和sbrk()改变程序中断的位置,它定义了进程数据段的结束.
这里的数据段意味着什么?是仅仅将数据段或数据,BSS和堆组合在一起?
根据维基:
有时,数据,BSS和堆区域统称为"数据段".
我认为没有理由改变数据段的大小.如果它是数据,BSS和堆集合那么它是有意义的,因为堆将获得更多的空间.
这让我想到了第二个问题.在我到目前为止阅读的所有文章中,作者都说堆积增长,堆栈向下增长.但是他们没有解释的是当堆占用堆和堆栈之间的所有空间时会发生什么?

zwo*_*wol 223
在您发布的图表中,"break" - 由brkand 操纵的地址和sbrk- 是堆顶部的虚线.

您阅读的文档将此描述为"数据段"的结尾,因为在传统的(预共享库,预先mmap)Unix中,数据段与堆连续; 在程序启动之前,内核会将"文本"和"数据"块加载到RAM中,从地址0开始(实际上略高于地址0,因此NULL指针确实没有指向任何东西)并将中断地址设置为数据段的结尾.然后第一次调用malloc将sbrk用于移动分解并在数据段的顶部和新的更高的中断地址之间创建堆,如图所示,并且随后的使用malloc将使用它来使堆更大有必要的.
同时,堆栈从内存顶部开始并逐渐减少.堆栈不需要显式系统调用来使其更大; 或者它开始时分配给它的RAM尽可能多(这是传统的方法),或者堆栈下面有一个保留地址区域,内核在注意到写入时会自动分配RAM (这是现代方法).无论哪种方式,地址空间底部可能存在或可能不存在可用于堆栈的"保护"区域.如果这个区域存在(所有现代系统都这样做),它将被永久取消映射; 如果任一堆栈或堆试种到它,你就会得到一个分段错误.但是,传统上,内核并没有试图强制执行边界; 堆栈可能会成长为堆,或者堆可能会成长为堆栈,无论哪种方式,他们都会乱写彼此的数据,程序会崩溃.如果你很幸运,它会立即崩溃.
我不确定这个图中512GB的数字来自哪里.它意味着一个64位的虚拟地址空间,这与你在那里非常简单的内存映射不一致.一个真正的64位地址空间看起来更像这样:

这不是远程扩展,它不应该被解释为任何给定操作系统的确切方式(在我绘制之后我发现Linux实际上使可执行文件比我想象的更接近零地址和共享库在令人惊讶的高地址).该图的黑色区域未映射 - 任何访问都会导致立即的段错误 - 并且它们相对于灰色区域是巨大的.浅灰色区域是程序及其共享库(可以有数十个共享库); 每个都有一个独立的文本和数据段(和"bss"段,它也包含全局数据,但初始化为所有位零,而不是占用磁盘上可执行文件或库中的空间).堆不再必然与可执行文件的数据段连续 - 我这样绘制了它,但看起来Linux至少不会这样做.堆栈不再与虚拟地址空间的顶部挂钩,堆与堆栈之间的距离非常大,您无需担心跨越它.
中断仍然是堆的上限.然而,我没有表现出的是,在黑色的某个地方可能会有数十个独立的内存分配,mmap而不是brk.(操作系统会尽量远离该brk区域,以免它们发生碰撞.)
Cir*_*四事件 22
最小的可运行示例
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;
}
Run Code Online (Sandbox Code Playgroud)
用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;
}
Run Code Online (Sandbox Code Playgroud)
即使没有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;
}
Run Code Online (Sandbox Code Playgroud)
在Ubuntu 18.04上测试过.
虚拟地址空间可视化
之前brk:
+------+ <-- Heap Start == Heap End
Run Code Online (Sandbox Code Playgroud)
之后brk(p + 2):
+------+ <-- Heap Start + 2 * sizof(int) == Heap End
| |
| You can now write your ints
| in this memory area.
| |
+------+ <-- Heap Start
Run Code Online (Sandbox Code Playgroud)
之后brk(b):
+------+ <-- Heap Start == Heap End
Run Code Online (Sandbox Code Playgroud)
为了更好地理解地址空间,您应该熟悉分页:x86分页如何工作?
更多信息
brk以前是POSIX,但它在POSIX 2001中删除了,因此需要sbrk访问glibc包装器.
删除可能是由于引入brk,这是一个超集,允许分配多个范围和更多的分配选项.
在内部,内核决定进程是否可以拥有那么多内存,并为该用法指定内存页面.
sbrk并且brk是libc用于brk在POSIX系统中实现的常见底层机制.
这解释了堆栈与堆的比较:x86汇编中寄存器上使用的push/pop指令的功能是什么?
lus*_*oog 10
您可以使用brk和sbrk自己避免每个人总是在抱怨的"malloc开销".但是你不能轻易地使用这种方法,malloc因此它只适用于你没有free任何东西.因为你做不到.此外,您应该避免任何可能在malloc内部使用的库调用.IE浏览器.strlen可能是安全的,但fopen可能不是.
打电话sbrk就像你打电话一样malloc.它返回一个指向当前中断的指针,并将中断增加该数量.
void *myallocate(int n){
return sbrk(n);
}
Run Code Online (Sandbox Code Playgroud)
虽然你不能释放单独的分配(因为没有malloc开销,请记住),你可以通过调用第一次调用返回的值来释放整个空间,从而倒回brk.brksbrk
void *memorypool;
void initmemorypool(void){
memorypool = sbrk(0);
}
void resetmemorypool(void){
brk(memorypool);
}
Run Code Online (Sandbox Code Playgroud)
您甚至可以堆叠这些区域,通过将休息时间倒回到区域的开始来丢弃最近的区域.
还有一件事 ...
sbrk在代码高尔夫中也很有用,因为它短于2个字符malloc.
有一个特殊指定的匿名私有内存映射(传统上位于 data/bss 之外,但现代 Linux 实际上会使用 ASLR 调整位置)。原则上,它并不比您可以使用 创建的任何其他映射更好mmap,但 Linux 有一些优化,可以向上扩展此映射的末尾(使用brk系统调用),同时相对于mmap或mremap将产生的锁定成本降低。这使得malloc在实现主堆时使用它很有吸引力。
| 归档时间: |
|
| 查看次数: |
80042 次 |
| 最近记录: |