【问题标题】:Why malloc(1) gives more than one page size?为什么 malloc(1) 给出的页面大小不止一个?
【发布时间】:2016-03-18 22:01:40
【问题描述】:

我尝试在我的机器上使用 sbrk(1),然后故意写出测试页面大小,即 4096 字节。但是当我调用 malloc(1) 时,我在访问 135152 字节后得到了 SEGV,这远远超过一页大小。我知道 malloc 是库函数,它依赖于实现,但考虑到它最终会调用 sbrk,为什么它会给出一个以上的页面大小。谁能告诉我它的内部工作原理?

我的操作系统是 ubuntu 14.04,我的架构是 x86

更新:现在我想知道是否是因为 malloc 将地址返回到一个足够大以容纳我的数据的空闲列表块。但是那个地址可能在堆的中间,这样我就可以一直写,直到达到堆的上限。

【问题讨论】:

  • 您具体使用的是什么操作系统和架构?另外,你确定 malloc() 调用 sbrk() 吗?
  • 我不确定,但我的教授和一些在线资源告诉我。我在 ubuntu 14.04 下使用 x86 架构
  • ITYM 4096,而不是 4086。“(1)”是什么意思? sbrk 是系统调用 (2),malloc 是库函数 (3)。
  • 好吧,我只能推测您的特定 malloc(),但我经常看到人们假设“x 库函数在后台调用 y”,而这不一定是真的(例如,十年前可能 x() 称为 y(),但此后情况发生了变化)。另外,您是否尝试过在 malloc() 中设置断点,然后单步执行以查看它调用了什么?
  • 我认为原因正是您在“更新:”下写的内容:

标签: c malloc sbrk


【解决方案1】:

较旧的malloc() UNIX 实现使用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) 分配必须是页面对齐的;内核必须 执行将通过分配的内存清零的昂贵任务 地图(2)。平衡这些因素导致默认设置 M_MMAP_THRESHOLD 参数为 128*1024。

此参数的下限为0。上限为 DEFAULT_MMAP_THRESHOLD_MAX:32 位系统上为 512*1024 或 在 64 位系统上为 4*1024*1024*sizeof(long)。

注意:现在,glibc 默认使用动态 mmap 阈值。 阈值初始值为128*1024,但是当blocks 大于当前阈值且小于或等于 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 ;-)

【讨论】:

  • 谢谢,您的回答有帮助
【解决方案2】:

malloc 出于性能原因在大块中分配内存。随后对malloc 的调用可以为您提供大块的内存,而不必向操作系统询问很多小块。这减少了所需的系统调用次数。

来自this article

当进程需要内存时,通过使用 brk() 或 sbrk() 系统调用向前移动堆的上限来创建一些空间。因为系统调用在 CPU 使用方面的成本很高,所以更好的策略是调用 brk() 来获取大块内存,然后根据需要拆分它以获得更小的块。这正是 malloc() 所做的。它将许多较小的 malloc() 请求聚合到较少的大型 brk() 调用中。这样做可以显着提高性能。

请注意,malloc 的一些现代实现使用 mmap 而不是 brk/sbrk 来分配内存,但除此之外,上述情况仍然适用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-23
    • 2016-07-18
    • 2016-01-22
    • 2016-09-25
    • 1970-01-01
    • 1970-01-01
    • 2019-12-19
    • 1970-01-01
    相关资源
    最近更新 更多