【问题标题】:what does "malloc_trim(0)" really mean?“malloc_trim(0)”到底是什么意思?
【发布时间】:2023-03-03 02:05:01
【问题描述】:

manual page告诉了我很多,通过它我知道了很多“glibc”内存管理的背景知识。

但我仍然感到困惑。 “ma​​lloc_trim(0)”(注意零作为参数)是否意味着(1.)“堆”部分中的所有内存都将返回给操作系统?或者(2.)只是堆的最顶部区域的所有“未使用”内存都将返回给操作系统?

如果答案是 (1.),如果堆中仍然使用的内存呢?如果堆在某个地方使用过内存,它们会被淘汰,还是函数无法成功执行?

如果答案是 (2.),那么那些“holes”在一些地方而不是堆的顶部呢?它们不再是未使用的内存,但是堆的最顶部区域仍在使用,这个调用会有效吗?

谢谢。

【问题讨论】:

    标签: malloc glibc trim sbrk


    【解决方案1】:

    malloc_trim 的手册页说它会释放空闲内存,所以如果堆中有分配的内存,它不会释放整个堆。如果您知道您仍然需要一定数量的内存,则该参数就在那里,因此释放更多内存将导致 glibc 稍后不得不做不必要的工作。

    至于漏洞,这是内存管理和将内存返回给操作系统的标准问题。程序可用的主要低级堆管理是brksbrk,它们所能做的就是通过更改顶部来扩展或缩小堆区域。所以他们没有办法将漏洞返回给操作系统。一旦程序调用sbrk 分配更多堆,则只有在该空间的顶部空闲并且可以交还的情况下才能返回该空间。

    请注意,还有其他更复杂的内存分配方式(例如,使用匿名 mmap),其约束可能与基于 sbrk 的分配不同。

    【讨论】:

    • 现在有一种方法可以将堆中间的空洞返回给操作系统:MADV_DONTNEED(有时是 MADV_FREE):code.metager.de/source/xref/gnu/glibc/malloc/malloc.c#4535mtrim (mstate av, size_t pad) ... __madvise (paligned_mem, size & ~psm1, MADV_DONTNEED);madvise 带有这样的标志将页面标记为应用程序不需要的页面,操作系统可能会破坏其中的数据并取消映射物理空间;对页面的下一次访问可能会生成页面错误以将虚拟页面重新映射到物理空间。
    • 代码被添加到sourceware.org/git/?p=glibc.git;a=commit;f=malloc/… 68631c8eb92ff38d9da1ae34f6aa048539b199cc "(mTRIm): 另外遍历所有空闲块并使用 madvise 为所有包含至少一个内存页的块释放内存。" - Ulrich Drepper 2007 年 12 月 16 日(glibc 2.9)
    【解决方案2】:

    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.cmalloc_trim cmets 来自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 中的文本,也没有记录在手册页项目中。 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)。

    2007 年 12 月,Ulrich Drepper 提交了 https://sourceware.org/git/?p=glibc.git;a=commit;f=malloc/malloc.c;h=68631c8eb92ff38d9da1ae34f6aa048539b199cc(它是 glibc 2.9 和更新版本的一部分),它更改了 mtrim 的实现(但它没有更改任何文档或手册页,因为其中没有手册页glibc):

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

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

    块的未使用部分(任何地方,包括中间的块),在页面大小上对齐并且大小超过页面可以标记为MADV_DONTNEEDhttps://sourceware.org/git/?p=glibc.git;a=blobdiff;f=malloc/malloc.c;h=c54c203cbf1f024e72493546221305b4fd5729b7;hp=1e716089a2b976d120c304ad75dd95c63737ad75;hb=68631c8eb92ff38d9da1ae34f6aa048539b199cc;hpb=52386be756e113f20502f181d780aecc38cbb66a

           INTERNAL_SIZE_T size = chunksize (p);
    
           if (size > psm1 + sizeof (struct malloc_chunk))
             {
               /* 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);
            }
    

    这是现在 glibc 中 madviseMADV_DONTNEED 的两种用法之一,一种用于堆的顶部(shrink_heap),另一种用于标记任何块(mtrim):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);
    

    我们可以使用这个简单的 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);
        // check that all arrays are allocated on the heap and not with mmap
        printf("1:%p 2:%p 3:%p 4:%p\n", m1, m2, m3, m4);
        // free 40000 bytes in the middle
        free(m2);
        // call trim (same result with 2000 or 2000000 argument)
        malloc_trim(0);
        // call some syscall to find this point in the strace output
        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
    ...
    nanosleep({1, 0}, 0x7ffffafbfff0)       = 0
    brk(0xceb000)                           = 0xceb000
    

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

    【讨论】:

    猜你喜欢
    • 2017-08-07
    • 2017-07-20
    • 2014-09-23
    • 2014-07-25
    • 2012-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多