如何在多线程中关闭malloc()的mmap使用?

4

似乎在多线程程序中,malloc()更倾向于使用mmap()来分配空间。我尝试设置M_TRIM_THRESHOLDM_MMAP_MAX以关闭mmap的使用,但失败了:

// Turn off malloc trimming.
mallopt(M_TRIM_THRESHOLD, -1);

// Turn off mmap usage.
mallopt(M_MMAP_MAX, 0);

一个简单的测试代码示例如下:
#include <malloc.h>
#include <cassert>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

void alloc_assert()
{
    // Turn off malloc trimming.
    mallopt(M_TRIM_THRESHOLD, -1);

    // Turn off mmap usage.
    mallopt(M_MMAP_MAX, 0);

    void* p = malloc(100);

    printf("size_t(p): %zu\n", size_t(p));

    assert(size_t(p) < 0x100000000000l);
}

void* thread_func(void *arg)
{
    alloc_assert();

    pthread_exit(NULL);

    return NULL;
}

int main()
{
    pthread_t thr[2];
    int data = 0;

    // Multi-thread enabled.
    if (pthread_create(&thr[0], NULL, &thread_func, (void*) &data) != 0)
    {
        printf("Create thread error\n");
    }

    pthread_join(thr[0], NULL);

    //alloc_assert();

    return 0;
}

以下是输出结果:
size_t(p): 140154111002816
a.out: main.cpp:37: void alloc_assert(): Assertion `size_t(p) < 0x100000000000l' failed.
[1]    154060 abort      ./a.out

malloc()在高地址分配空间,而不是普通堆地址。然而,如果我们更改main()中的代码如下:

int main()
{
    alloc_assert();

    return 0;
}

输出结果为:

size_t(p): 31775776

使用malloc()分配空间的方式是在普通堆上分配,而不是使用mmap()。我想知道在多线程程序中是否有可能关闭malloc()mmap()的使用。

我的环境配置:

Thread model: posix
gcc version 5.2.0 (GCC)
Linux fsdev32 2.6.32-573.el6.x86_64

@EOF 我们保留了大于0x100000000000l的地址来存储我们的系统数据。通过mmap()进行的malloc()将破坏我们的数据,因为它也会占用这些地址。 - Jim Ma
6
如果您需要特殊的地址范围,先使用mmap映射它们。 - Zan Lynx
你不能使用mmap来请求特定的页面吗? - k_kaz
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Zan Lynx
@JimMa:试图修复malloc不使用mmap的问题在于malloc并不是唯一使用mmap的程序。未来你可能会升级其他库,它们将自行映射内存,并从高地址返回指针,绕过malloc...那么你该怎么办呢? - Zan Lynx
显示剩余9条评论
1个回答

3

概要

看起来原因是glibc的malloc使用多个malloc "arena",并将每个新线程都分配一个独立的arena,无论您设置M_MMAP_MAX为什么值。

如果arena中有空间,glibc会将其提供给您的malloc()请求。

您可以通过mallopt(M_ARENA_MAX, 1)来禁用它。

详情

我通过导入#include <malloc.h>(glibc特定)并更改您的代码以显示malloc_stats()统计信息来确认了这一点:

malloc_stats();
puts("");
void* p = malloc(100);
malloc_stats();

这将打印:

Arena 0:
system bytes     =     135168
in use bytes     =       1328
Total (incl. mmap):
system bytes     =     135168
in use bytes     =       1328
max mmap regions =          0
max mmap bytes   =          0

Arena 0:
system bytes     =     135168
in use bytes     =       1328
Arena 1:
system bytes     =     135168
in use bytes     =       2336
Total (incl. mmap):
system bytes     =     270336
in use bytes     =       3664
max mmap regions =          0
max mmap bytes   =          0

我们可以看到一块新的Arena 1被添加了135168个字节。

strace -fy -e mmap ./yourprogram中,我们可以看到这个Arena的创建:

[pid  8704] mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7efefe1a7000
[pid  8704] munmap(0x7efefe1a7000, 31821824) = 0
[pid  8704] munmap(0x7eff04000000, 35287040) = 0
[pid  8704] mprotect(0x7eff00000000, 135168, PROT_READ|PROT_WRITE) = 0

size_t(p): 139633681762496; address: 0x7eff000008c0

看起来 glibc 为新的 arena 映射了一些内存,释放了其中的一部分,然后使用 mprotect() 保护了 135168 字节的内存 - 就像该 arena 在 malloc_stats() 中显示的一样。这提示我想到了 man mallopt 所说的。
Setting this parameter to 0 disables the use of mmap(2)
for servicing large allocation requests.

实际上这种说法并不完全正确:当系统需要创建一个新的内存区域时,它仍然会使用mmap方法。

但是,你可以通过以下方式避免这种情况的发生:

mallopt(M_ARENA_MAX, 1);

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