我已经尝试在我的机器上使用sbrk(1)然后故意写出超出测试页面大小,即4096字节。但是当我调用malloc(1)时,我在访问135152字节后获得了SEGV,这超过了一个页面大小。我知道malloc是库函数,它依赖于实现,但考虑到它最终会调用sbrk,为什么它会给出多个页面大小。谁能告诉我它的内部工作?
我的操作系统是ubuntu 14.04,我的架构是x86
更新:现在我想知道是不是因为malloc将地址返回到足以容纳我的数据的空闲列表块。但是该地址可能位于堆的中间,这样我就可以继续写入,直到达到堆的上限。
答案 0 :(得分:8)
UNIX的较早malloc()
实现使用sbrk()
/ brk()
系统调用。但是现在,实现使用mmap()
和sbrk()
。 glibc的malloc()
实现(可能是你在Ubuntu 14.04上使用的那个)同时使用sbrk()
和mmap()
以及在你请求时使用哪一个来分配通常取决于分配请求的大小,glibc动态地执行。
对于小分配,glibc使用sbrk()
,对于较大的分配,它使用mmap()
。宏M_MMAP_THRESHOLD
用于决定这一点。目前,它的默认值为set to 128K。这解释了为什么你的代码设法分配135152字节,因为它大约是~128K。即使您只请求了1个字节,您的实现也会分配128K以进行有效的内存分配。因此,在超过此限制之前,不会发生段错误。
您可以使用mallopt()
通过更改默认参数来使用M_MAP_THRESHOLD
。
M_MMAP_THRESHOLD
对于大于或等于限制的分配 由M_MMAP_THRESHOLD指定的(以字节为单位)无法满足 在空闲列表中,内存分配函数使用mmap(2)代替 使用sbrk(2)增加程序中断。
使用mmap(2)分配内存具有明显的优势 分配的内存块总是可以独立释放 到系统。 (相比之下,只有内存才可以修剪堆 在最高端被释放。)另一方面,有一些 使用mmap(2)的缺点:未放置释放空间 在免费列表上以供以后分配重用;记忆可能会浪费 因为mmap(2)分配必须是页面对齐的;和内核必须 执行将通过分配的内存归零的昂贵任务 MMAP(2)。平衡这些因素会导致默认设置为 M_MMAP_THRESHOLD参数为128 * 1024。
此参数的下限为0.上限为 DEFAULT_MMAP_THRESHOLD_MAX:32位系统上的512 * 1024或 64位系统上的4 * 1024 * 1024 * sizeof(长)。
注意:现在,glibc默认使用动态mmap阈值。 阈值的初始值是128 * 1024,但是当块时 大于当前阈值且小于或等于 释放DEFAULT_MMAP_THRESHOLD_MAX,调整阈值 向上移动到释放块的大小。动态mmap时 阈值处于有效状态,修剪堆的阈值也是 动态调整为动态mmap阈值的两倍。动态 如果任何一个,则禁用mmap阈值的调整 M_TRIM_THRESHOLD,M_TOP_PAD,M_MMAP_THRESHOLD或M_MMAP_MAX 参数已设定。
例如,如果你这样做:
#include<malloc.h>
mallopt(M_MMAP_THRESHOLD, 0);
在致电malloc()
之前,您可能会看到不同的限制。其中大部分都是实现细节,C标准表示将undefined behaviour写入您的进程不拥有的内存中。所以冒风险 - 否则,demons may fly out of your nose; - )
答案 1 :(得分:3)
malloc
以大块分配内存。对malloc
的后续调用可以为大块提供内存,而不必向操作系统询问大量小块。这减少了所需的系统调用次数。
来自this article:
当进程需要内存时,通过使用brk()或sbrk()系统调用向前移动堆的上限来创建一些空间。因为系统调用在CPU使用方面是昂贵的,所以更好的策略是调用brk()来获取大块内存,然后根据需要将其拆分以获得更小的块。这正是malloc()所做的。它将许多较小的malloc()请求聚合为较少的大型brk()调用。这样做可以显着提高性能。
请注意,malloc
的一些现代实现使用mmap
而不是brk
/ sbrk
来分配内存,但除此之外仍然如此。