18

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

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

如果答案是 ( 1. ),如果堆中仍然使用的内存怎么办?如果堆在某个地方使用了内存,它们会被消除,还是函数无法成功执行?

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

谢谢。

4

2 回答 2

13

手册页在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 glibc 的 git,所以 glibc 中没有手册页,也没有提交到 malloc_trim 手册页来记录这个补丁。glibc malloc 最好也是唯一的文档是它的源代码:https ://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c 有malloc_trim评论来自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_DONTNEED https://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);
        }

madvise这是现在 glibc 中with的两种用法之一MADV_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);

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

#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

因此,调用后madviseMADV_DONTNEED9 页malloc_trim(0),此时堆中间有 40008 字节的空洞。

于 2017-02-16T12:52:07.080 回答
6

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

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

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

于 2013-03-21T22:19:07.517 回答