malloc()使用brk()还是mmap()?

36

c 代码:

// program break mechanism
// TLPI exercise 7-1

#include <stdio.h>
#include <stdlib.h>

void program_break_test() {
    printf("%10p\n", sbrk(0));

    char *bl = malloc(1024 * 1024);
    printf("%x\n", sbrk(0));

    free(bl);
    printf("%x\n", sbrk(0));

}

int main(int argc, char **argv) {
    program_break_test();
    return 0;
}

当编译以下代码时:

 printf("%10p\n", sbrk(0));

我收到了一个警告提示:

格式化字符串‘%p’需要的参数类型是‘void *’,但是第二个参数的类型是‘int’

问题1:为什么会出现这个提示?


但是在我执行了 malloc(1024 * 1024) 之后,程序似乎没有改变 break。

以下是输出:

9b12000
9b12000
9b12000

问题2:当启动时,这个过程是否会在堆上分配内存以备将来使用?或者编译器改变了分配的时间点?如果没有,为什么?


[更新] 总结:brk() 或 mmap()

在查看 TLPI 和 man 页面(得到 TLPI 作者的帮助)后,现在我理解了 malloc() 如何决定使用 brk()mmap(),如下:

mallopt() 可以设置参数来控制 malloc() 的行为,其中有一个参数名为 M_MMAP_THRESHOLD,一般而言:

  • 如果请求的内存小于该值,则使用 brk()
  • 如果请求的内存大于或等于该值,则使用 mmap()

该参数的默认值为 128kb(在我的系统上),但在我的测试程序中,我使用了 1Mb,因此选择了 mmap()。当我将请求的内存更改为 32kb 时,我看到了将使用 brk()

该书在 TLPI 第 147 和 1035 页提到了这一点,但我没有仔细阅读那部分内容。

关于该参数的详细信息可在 mallopt() 的 man 页面中找到。


3
这行代码是C语言中的预处理指令,用于包含unistd.h头文件。 - JS1
2
你需要 unistd.h 中的 sbrk() 原型。如果没有原型,编译器会假定未知函数返回 int - JS1
@JS1 我在想,既然我错过了头文件,它不应该会给出编译错误吗?但是它没有... - Eric
1
@EricWang:如果你使用的是gcc或clang编译器,那么加上-Wall参数会产生警告。你应该总是使用-Wall参数进行编译。 - rici
@rici 是的,我刚刚尝试使用-Wall编译,并看到了警告warning: implicit declaration of function ‘sbrk’,非常好的提示! - Eric
显示剩余2条评论
3个回答

20
如果我们更改程序以查看 malloc 分配的内存位置:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void program_break_test() {
  printf("%10p\n", sbrk(0));

  char *bl = malloc(1024 * 1024);
  printf("%10p\n", sbrk(0));
  printf("malloc'd at: %10p\n", bl);

  free(bl);
  printf("%10p\n", sbrk(0));

}

int main(int argc, char **argv) {
  program_break_test();
  return 0;
}

也许更清晰的是,sbrk 不会改变。由 malloc 分配给我们的内存被映射到一个完全不同的位置。

你还可以在 Linux 上使用 strace 查看调用了哪些系统调用,并发现 malloc 使用 mmap 来进行分配。


1
我发现有一个“THRESHOLD”来控制是否使用brk()mmap(),我在问题中更新了它。 - Eric

8
malloc不仅局限于使用sbrk来分配内存。例如,它可能使用mmap来映射一个大的MAP_ANONYMOUS内存块;通常情况下,mmap会分配一个远离数据段的虚拟地址。
还有其他可能性。特别是,malloc作为标准库的核心部分,不仅局限于标准库函数;它可以利用操作系统特定的接口。

你是在说 mmap 可以分配比 sbrk 更多的内存吗?如果是这样,那么 sbrk 分配内存的位置在哪里,mmap 分配内存的位置又在哪里呢?+1 -- 很好的回答。 - xilpex
2
sbrk在特定位置分配内存;每次调用sbrk时,您都会获得与上一次调用相邻的一块内存。历史上,这是堆和栈之间的边界(堆从数据段向上增长,栈从进程地址空间的末尾向下增长)。mmap通常无法分配更多的内存,但它确实可以使用不同的地址和更多选项进行分配(内存保护标志、后备存储、巨大的虚拟块(以减少页表大小)等)。 - rici
@petercordes:没错,我确实是这个意思。谢谢。 - rici

6
如果您在代码中使用了malloc,它将在开始时调用brk(),从堆中分配了0x21000字节,这就是您打印的地址。因此问题1:以下malloc的要求可以从预分配的空间中满足,所以这些malloc实际上没有调用brk,这是malloc中的一种优化。如果下一次您想要分配超出该边界的大小,则会调用新的brk(如果不大于mmap阈值)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接