手册页告诉了我很多,通过它我知道了很多关于"glibc"内存管理的背景知识。
但我还是很困惑。"malloc_trim(0)"(注意参数为零)是否意味着(1.)"堆"部分中的所有内存将返回给操作系统?或者(2.)堆最顶部区域的所有"未使用"内存将返回给操作系统?
如果答案是(1.),如果堆中仍在使用的内存怎么办?如果堆在某个地方使用了内存,它们会被消除吗,或者函数不会成功执行?
如果答案是(2.),那么那些位于堆顶部而不是堆顶部的"洞"怎么办?它们不再是未使用的内存,但是堆的最上面的区域仍然被使用,这个调用是否有效?谢谢。
malloc_trim
的Man page在这里提交:https://github.com/mkerrisk/man-pages/blob/master/man3/malloc_trim.3,据我所知,它是由Man -pages项目维护者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.cmalloc/malloc.c
中有malloc_trim
注释:
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中的文本记录,也没有在man-pages项目中记录。2012年的手册页可能是该函数的第一个手册页,不是由glibc的作者编写的。glibc的信息页只提到M_TRIM_THRESHOLD为128 KB: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的commit 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):遍历所有竞技场并调用
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
和MADV_DONTNEED
在glibc中总共两种用法之一,一种用于堆的顶部(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("%sn","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:%pn", 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 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
...
nanosleep({1, 0}, 0x7ffffafbfff0) = 0
brk(0xceb000) = 0xceb000
因此,在malloc_trim(0)
调用之后,madvise
和MADV_DONTNEED
有9个页面,当堆中间有40008字节的洞时。
malloc_trim
的手册页说它释放空闲内存,所以如果堆中有分配的内存,它不会释放整个堆。如果您知道您仍然需要一定数量的内存,则使用该参数,因此释放更多的内存将导致glibc稍后不得不做不必要的工作。
至于漏洞,这是内存管理和将内存返回给操作系统的标准问题。程序可用的主要低级堆管理是brk
和sbrk
,它们所能做的就是通过改变堆的顶部来扩展或缩小堆区域。所以他们不可能在操作系统上留下漏洞;一旦程序调用sbrk
来分配更多的堆,那么只有在该空间的顶部是空闲的并且可以交还的情况下,该空间才能被返回。
请注意,还有其他更复杂的方法来分配内存(例如使用匿名mmap
),它们可能具有与基于sbrk
的分配不同的约束。