【问题标题】:Can malloc_trim() release memory from the middle of the heap?malloc_trim() 可以从堆中间释放内存吗?
【发布时间】:2015-04-21 03:44:02
【问题描述】:

我对 glibc 中实现的 malloc_trim 的行为感到困惑。

man malloc_trim
[...]
malloc_trim - release free memory from the top of the heap
[...]
This function cannot release free memory located at places other than the top of the heap.

当我现在查找malloc_trim() 的源(在 malloc/malloc.c 中)时,我看到它调用了mtrim(),它利用madvise(x, MADV_DONTNEED) 将内存释放回操作系统。

所以我想知道手册页是否错误,或者我是否误解了 malloc/malloc.c 中的源代码。

malloc_trim()可以从堆中间释放内存吗?

【问题讨论】:

  • 不是在文档中吗? This function cannot release free memory located at places other than the top of the heap.
  • @tangrs 问题是,从实际实现来看,我认为文档是错误的(但我不太确定)。
  • 始终聆听文档。函数的记录行为永远不会改变,但实现可以。依赖于使用特定实现的函数可能意味着您的程序将在未来版本或不同平台上中断。
  • 我关心特定实现的行为。在我的问题中,我说我在谈论 glibc。
  • 没有什么能阻止实现在 glibc 版本之间发生变化...

标签: linux memory operating-system malloc glibc


【解决方案1】:

现在glibc中madviseMADV_DONTNEED有两种用法:http://code.metager.de/source/search?q=MADV_DONTNEED&path=%2Fgnu%2Fglibc%2Fmalloc%2F&project=gnu

 H A D  arena.c 643 __madvise ((char *) h + new_size, diff, MADV_DONTNEED);
 H A D  malloc.c    4535 __madvise (paligned_mem, size & ~psm1, MADV_DONTNEED);

Ulrich Drepper 于 2007 年 12 月 16 日提交了https://sourceware.org/git/?p=glibc.git;a=commit;f=malloc/malloc.c;h=68631c8eb92ff38d9da1ae34f6aa048539b199cc(glibc 2.9 和更新版本的一部分):

  • malloc/malloc.c (public_mTRIm):遍历所有 arenas 并调用

mTRIm 适用于所有这些。 (mTRIm):另外迭代所有空闲块并使用 madvise 为所有包含至少一个块的块释放内存 内存页。

mTRIm(现在是mtrim)实现已更改。块中未使用的部分,在页面大小上对齐并且大于页面的大小可以标记为MADV_DONTNEED

           /* See whether the chunk contains at least one unused page.  */
           char *paligned_mem = (char *) (((uintptr_t) p
                                           + sizeof (struct malloc_chunk)
                                           + psm1) & ~psm1);

           assert ((char *) chunk2mem (p) + 4 * SIZE_SZ <= paligned_mem);
           assert ((char *) p + size > paligned_mem);

           /* This is the size we could potentially free.  */
           size -= paligned_mem - (char *) p;

           if (size > psm1)
               madvise (paligned_mem, size & ~psm1, MADV_DONTNEED);

malloc_trim 的手册页在那里:https://github.com/mkerrisk/man-pages/blob/master/man3/malloc_trim.3,它是由 kerrisk 在 2012 年提交的:https://github.com/mkerrisk/man-pages/commit/a15b0e60b297e29c825b7417582a33e6ca26bf65

我可以grep the glibc's git, there are no man pages in the glibc,并且没有提交到 malloc_trim 手册页来记录这个补丁。 glibc malloc 最好也是唯一的文档是它的源码:https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c

Additional functions:
 malloc_trim(size_t pad);
 609 /*
 610   malloc_trim(size_t pad);
 611 
 612   If possible, gives memory back to the system (via negative
 613   arguments to sbrk) if there is unused memory at the `high' end of
 614   the malloc pool. You can call this after freeing large blocks of
 615   memory to potentially reduce the system-level memory requirements
 616   of a program. However, it cannot guarantee to reduce memory. Under
 617   some allocation patterns, some large free blocks of memory will be
 618   locked between two used chunks, so they cannot be given back to
 619   the system.
 620 
 621   The `pad' argument to malloc_trim represents the amount of free
 622   trailing space to leave untrimmed. If this argument is zero,
 623   only the minimum amount of memory to maintain internal data
 624   structures will be left (one page or less). Non-zero arguments
 625   can be supplied to maintain enough trailing space to service
 626   future expected allocations without having to re-obtain memory
 627   from the system.
 628 
 629   Malloc_trim returns 1 if it actually released any memory, else 0.
 630   On systems that do not support "negative sbrks", it will always
 631   return 0.
 632 */
 633 int      __malloc_trim(size_t);
 634 

从块的中间释放没有记录为 malloc/malloc.c 中的文本(并且在 2007 年没有更新推荐中的 malloc_trim 描述),也没有记录在手册页项目中。 2012 年的手册页可能是该函数的第一个手册页,不是由 glibc 的作者编写的。 glibc 的信息页面仅提及 128 KB 的 M_TRIM_THRESHOLD: https://www.gnu.org/software/libc/manual/html_node/Malloc-Tunable-Parameters.html#Malloc-Tunable-Parameters 并且不列出 malloc_trim 函数 https://www.gnu.org/software/libc/manual/html_node/Summary-of-Malloc.html#Summary-of-Malloc (并且它也没有记录 memusage/memusagestat/libmemusage.so)。

您可以再次询问 Drepper 和其他 glibc 开发人员,就像您在 https://sourceware.org/ml/libc-help/2015-02/msg00022.html“malloc_trim() 行为”中所做的那样,但仍然没有得到他们的回复。 (仅来自其他用户的错误答案,例如 https://sourceware.org/ml/libc-help/2015-05/msg00007.html https://sourceware.org/ml/libc-help/2015-05/msg00008.html

或者您可以使用这个简单的 C 程序 (test_malloc_trim.c) 和 strace/ltrace 来测试 malloc_trim

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

int main()
{
    int *m1,*m2,*m3,*m4;
    printf("%s\n","Test started");
    m1=(int*)malloc(20000);
    m2=(int*)malloc(40000);
    m3=(int*)malloc(80000);
    m4=(int*)malloc(10000);
    printf("1:%p 2:%p 3:%p 4:%p\n", m1, m2, m3, m4);
    free(m2);
    malloc_trim(0); // 20000, 2000000
    sleep(1);
    free(m1);
    free(m3);
    free(m4);
    // malloc_stats(); malloc_info(0, stdout);
    return 0;
}

gcc test_malloc_trim.c -o test_malloc_trim, strace ./test_malloc_trim

write(1, "Test started\n", 13Test started
)          = 13
brk(0)                                  = 0xcca000
brk(0xcef000)                           = 0xcef000
write(1, "1:0xcca010 2:0xccee40 3:0xcd8a90"..., 441:0xcca010 2:0xccee40 3:0xcd8a90 4:0xcec320
) = 44
madvise(0xccf000, 36864, MADV_DONTNEED) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({1, 0}, 0x7ffffafbfff0)       = 0
brk(0xceb000)                           = 0xceb000

所以,在malloc_trim(0) 调用后,有madviseMADV_DONTNEED 9 页,此时堆中间有40008 字节的空洞。

【讨论】:

【解决方案2】:

... 利用 madvise(x, MADV_DONTNEED) 将内存释放回 操作系统。

madvise(x, MADV_DONTNEED) 不会释放内存。 man madvise:

MADV_DONTNEED
不要期望在不久的将来访问。 (暂且, 应用程序在给定的范围内完成,所以内核 可以释放与其相关的资源。)的后续访问 此范围内的页面将成功,但会导致 从底层映射文件重新加载内存内容 (参见 mmap(2))或按需零填充页面的映射 一个基础文件。

所以,madvise(x, MADV_DONTNEED) 的用法与man malloc_trim 的说法并不矛盾:

这个函数不能释放堆顶以外的空闲内存。

【讨论】:

  • 关于 madvise(x, MADV_DONTNEED) 不释放内存。对给定范围调用它的结果是操作系统可以释放底层页面。将有更少的页面提交到相应的过程。对我来说,这归结为释放内存。 ;-) 相应进程中的映射将保留(尽管指向 COW 零页)。如果我的进程将新数据放入给定范围内,内核将必须提供一些支持此操作的页面(向我提交内存)。
  • 如果页面映射仍然存在,那么它们所代表的进程虚拟内存还没有真正释放,是吧!该进程并未从其地址空间中永久归还页面,因此它没有“释放”它们。充其量它告诉内核它的 RSS 可以减少,但它实际上并没有减少它的 VSZIZE,所以它实际上并没有“释放”任何页面。理解“释放”在堆和malloc() 方面的含义的关键在于,当它说它将“释放位于......堆顶部的空闲内存”时,它意味着“它将减少 VSIZE”。
猜你喜欢
  • 2011-07-21
  • 2016-07-25
  • 2016-07-14
  • 2015-06-26
  • 2018-11-29
  • 1970-01-01
  • 2012-09-17
  • 2016-05-07
  • 1970-01-01
相关资源
最近更新 更多