我对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()
能否从堆的中间释放内存?
在glibc中,madvise
和MADV_DONTNEED
现在有两种用法:http://code.metager.de/source/search?q=MADV_DONTNEED&路径=%2Fgnu%2Fglibc%2Fmalloc%2F&项目=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);
有https://sourceware.org/git/?p=glibc.git;a=提交;f=malloc/malloc.c;h=68631c8eb92ff38d9da1ae34f6a048539b199cc由Ulrich Drepper于2007年12月16日提交(glibc 2.9及更新版本的一部分):
- malloc/malloc.c(public_mTRIm):遍历所有arena并调用
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.3kerrisk于2012年承诺:https://github.com/mkerrisk/man-pages/commit/a15b0e60b297e29c825b7417582a33e6ca26bf65
由于我可以grep glibc的git,所以glibc中没有手册页,也没有提交malloc_trim手册页来记录这个补丁。glibcmalloc最好也是唯一的文档是它的源代码:https://sourceware.org/git/?p=glibc.git;a=斑点;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中,从块的中间释放并没有作为文本进行记录(而commit中的malloc_trim
描述在2007年没有更新),也没有在手册页项目中进行记录。2012年的手册页可能是该功能的第一个手册页,不是由glibc的作者编写的。glibc的信息页面只提到128 KB的M_TRIM_THRESHOLD:https://www.gnu.org/software/libc/manual/html_node/Malloc-Tunable-Parameters.html#Malloc-可调参数和不列出malloc_trim函数https://www.gnu.org/software/libc/manual/html_node/Summary-of-Malloc.html#Summary-它也没有记录memusagestat/libmemousagest.so)
你可以再次询问Drepper和其他能说会道的开发人员,就像你在https://sourceware.org/ml/libc-help/2015-02/msg00022.html"malloc_trim()behavior",但他们仍然没有回复。(只有来自其他用户的错误答案,如https://sourceware.org/ml/libc-help/2015-05/msg00007.htmlhttps://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("%sn","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:%pn", 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 startedn", 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)
调用之后,当堆的中间有40008字节的洞时,9页的madvise
和MADV_DONTNEED
。
利用madvise(x,MADV_DONTNEED)将内存释放回操作系统。
madvise(x, MADV_DONTNEED)
不会释放内存。man madvise
:
MADV_DONTNED
不要指望在不久的将来访问。(就目前而言,应用程序在给定的范围内完成,因此内核可以释放与其关联的资源。)的后续访问此范围内的页面将成功,但会导致从底层映射文件重新加载内存内容(请参见mmap(2))或零填充按需页面,以获取没有底层文件。
因此,madvise(x, MADV_DONTNEED)
的使用与man malloc_trim
的声明并不矛盾:
此函数无法释放位于堆顶部以外位置的可用内存。