根据Linux程序员手册:
brk()和sbrk()改变程序中断的位置,它定义了进程数据段的结束。
这里的数据段是什么意思?它只是数据段还是数据、BSS和堆的组合?
根据wiki Data segment:
有时数据、BSS和堆区域统称为“数据段”。
我认为没有理由只改变数据段的大小。如果它是数据,BSS和堆的集合,那么它是有意义的,因为堆将获得更多的空间。
这就引出了我的第二个问题到目前为止,在我读过的所有文章中,作者都说堆是向上增长的,堆栈是向下增长的。但是他们没有解释的是,当堆占据了堆和栈之间的所有空间时会发生什么?
8条答案
按热度按时间1l5u6lss1#
在您发布的图表中,“break”(由
brk
和sbrk
操作的地址)是堆顶部的虚线。你读过的文档将此描述为“数据段”的结束,因为在传统的(预共享库,预
mmap
)Unix中,数据段与堆是连续的;在程序开始之前,内核会从地址0开始将“text”和“data”块加载到RAM中(实际上是略高于地址0,这样NULL指针就不会指向任何东西),并将中断地址设置为数据段的末尾。第一次调用malloc
将使用sbrk
来移动break,并在数据段的顶部和新的更高的break地址之间创建堆,如图所示,随后使用malloc
将根据需要使用它来使堆更大。同时,堆栈从内存的顶部开始向下增长。堆栈不需要显式的系统调用来使其更大;要么它开始时分配给它尽可能多的RAM(这是传统的方法),要么在堆栈下面有一个保留地址区域,当内核注意到有人试图写入时,它会自动分配RAM(这是现代的方法)。无论哪种方式,在地址空间的底部可能有也可能没有可用于堆栈的“保护”区域。如果这个区域存在(所有现代系统都这样做),它将永久未Map;如果栈或堆试图增长到它里面,你会得到一个分段错误。然而,传统上,内核并不试图强制边界;堆栈可以增长为堆,或者堆可以增长为堆栈,无论哪种方式,它们都会在彼此的数据上乱涂乱画,程序就会崩溃。如果你很幸运,它会立即崩溃。
我不知道这个图中的512 GB数字是从哪里来的。它意味着一个64位的虚拟地址空间,这与您拥有的非常简单的内存Map不一致。一个真实的64位地址空间看起来更像这样:
这不是远程缩放,它不应该被解释为任何给定的操作系统是如何做的(在我画了它之后,我发现Linux实际上把可执行文件放在比我想象的更接近地址零的地方,共享库的地址高得惊人)。图中的黑色区域是未Map的--任何访问都会立即导致segfault --它们相对于灰色区域来说非常巨大。浅灰色区域是程序及其共享库(可能有几十个共享库);每一个都有一个独立的文本和数据段(以及“0”段,它也包含全局数据,但被初始化为全位零,而不是占用磁盘上的可执行文件或库中的空间)。堆不再需要与可执行文件的数据段保持连续--我是这样画的,但看起来至少Linux不这样做。堆栈不再与虚拟地址空间的顶部挂钩,堆和堆栈之间的距离是如此之大,以至于您不必担心跨越它。
中断仍然是堆的上限。然而,我没有展示的是,在某处可能有几十个独立的内存分配,使用
mmap
而不是brk
。(操作系统将尝试使这些远离brk
区域,以便它们不会碰撞。svgewumm2#
最小可运行示例
brk()系统调用是做什么的?
请求内核允许您对称为堆的连续内存块进行读写。
如果你不问,当你试图从那个区域读和写的时候,它可能会给你定位错误。
没有
brk
:brk
:GitHub upstream。
上面的代码可能不会命中新页面,即使没有
brk
也不会发生segfault,所以这里有一个更激进的版本,它分配了16 MiB,并且很可能在没有brk
的情况下发生segfault:在Ubuntu 18.04上测试。
虚拟地址空间可视化
brk
之前:brk(p + 2)
之后:brk(b)
之后:为了更好地理解地址空间,您应该熟悉分页:How does x86 paging work?。
为什么我们需要
brk
和sbrk
?brk
当然可以用sbrk
+偏移计算来实现,两者的存在只是为了方便。在后端,Linux内核v5.0有一个系统调用
brk
,用于实现以下两个功能:https://github.com/torvalds/linux/blob/v5.0/arch/x86/entry/syscalls/syscall_64.tbl#L23是
brk
POSIX吗?brk
曾经是POSIX,但在POSIX 2001中被删除,因此需要_GNU_SOURCE
访问glibc Package 器。删除可能是由于引入了
mmap
,这是一个允许分配多个范围和更多分配选项的超集。我认为没有有效的情况下,你应该使用
brk
而不是malloc
或mmap
现在。brk
vsmalloc
brk
是实现malloc
的一种旧的可能性。mmap
是一种更新的更强大的机制,可能所有POSIX系统目前都使用它来实现malloc
。这是一个minimal runnablemmap
memory allocation example。我可以混合使用
brk
和malloc吗?如果你的
malloc
是用brk
实现的,我不知道这怎么可能不把事情搞砸,因为brk
只管理一个内存范围。但是我在glibc文档中找不到任何关于它的信息,例如:
我想事情可能只是在那里工作,因为
mmap
可能用于malloc
。另请参阅:
更多信息
在内部,内核决定进程是否可以拥有那么多内存,并为该用途指定memory pages。
这解释了堆栈与堆的比较:x86汇编中寄存器上使用的push / pop指令的功能是什么?
rqenqsqc3#
您可以自己使用
brk
和sbrk
来避免每个人都在抱怨的“malloc开销”。但是你不能很容易地将这个方法与malloc
结合使用,所以只有当你不需要free
任何东西的时候,它才是合适的。因为你不能。此外,您应该避免任何可能在内部使用malloc
的库调用。strlen
可能是安全的,但fopen
可能不是。调用
sbrk
就像调用malloc
一样。它返回一个指向当前断点的指针,并按该值递增断点。虽然不能释放单个分配(因为没有 malloc-overhead,请记住),但可以通过调用
brk
(使用第一次调用sbrk
时返回的值)来释放整个空间 ,从而 * 倒回brk。您甚至可以堆叠这些区域,通过将中断倒回区域的开始处来丢弃最近的区域。
sbrk
在code golf中也很有用,因为它比malloc
短2个字符。2ul0zpep4#
有一个特殊指定的匿名私有内存Map(传统上位于data/data之外,但现代Linux实际上会使用ASLR调整位置)。原则上,它并不比您可以使用
mmap
创建的任何其他Map更好,但是Linux有一些优化,可以向上扩展此Map的末尾(使用brk
系统调用),并减少相对于mmap
或mremap
的锁定成本。这使得malloc
实现在实现主堆时很有吸引力。yh2wf1be5#
堆放在程序数据段的最后。
brk()
用于更改(扩展)堆的大小。当堆不能再增长时,任何malloc
调用都将失败。myzjeezk6#
malloc使用brk系统调用来分配内存。
包括
用strace运行这个简单的程序,它将调用brk系统。
mznpcxlj7#
我可以回答你的第二个问题。Malloc将失败并返回空指针。这就是为什么在动态分配内存时总是检查空指针的原因。
r8uurelv8#
数据段是内存中保存所有静态数据的部分,在启动时从可执行文件读取,通常为零。