全部博文(73)
分类: LINUX
2009-04-23 16:56:25
关于glibc中内存回收的试验
零零散散的看了一些glibc堆管理的内部机制,大致清晰了,不过堆管理器是怎么将释放的内存返回给系统的。在前面的文章中提到了,glib的堆管理器在堆尾部空闲地址大于threshold时,就会返回给kernel,但一直没有找到。通过使用strace跟踪,也一直没有跟踪到系统释放内存,非常郁闷。
如果不释放的话,忽然想做一个试验,通过strace来进行跟踪:
试验一:
#include
#include
#include
int main(void)
{
char *p[1000];
int i=0;
for(i=0;i
strace日志如下:
前面有些调用在此省略掉了。
brk(0)
= 0x
brk(0x8930000)
= 0x8930000
brk(0)
=
0x8930000
brk(0)
=
0x8930000
brk(0x8951000)
= 0x8951000
brk(0)
=
0x8951000
brk(0x8972000)
= 0x8972000
brk(0)
=
0x8972000
brk(0x8993000)
= 0x8993000
brk(0)
=
0x8993000
brk(0x89b4000)
= 0x89b4000
brk(0)
=
0x89b4000
brk(0x89d5000)
= 0x89d5000
brk(0)
=
0x89d5000
brk(0x
brk(0)
= 0x
brk(0x
brk(0)
= 0x
brk(0)
= 0x
brk(0x8930000)
= 0x8930000
brk(0)
=
0x8930000
exit_group(0)
= ?
一个疑问:在扩展堆的时候,不是以页为单位,而是一下子申请了0x21000页,这个不清晰为什么。
注意:在最后,一个brk调用将堆栈全部释放了,这说明堆栈是能释放的,通过系统调用brk。另一方面,他是一次性释放,而不是分几步,说明当堆栈末尾更有内容时,是不释放堆栈的。
那么glibc是在什么时候触发将堆栈释放呢?
在新的glibc中glibc库中堆内存管理采用了Wolfram Gloger的ptmalloc/ptmalloc2代码.ptmalloc2代码是从Doug Lea的代码移植过来的,主要目的是增加对多线程(尤其是SMP系统)环境的支持,同时进一步优化了内存分配,回收的算法.由于在ptmalloc2中引入了fastbins机制。
关于fastbins理解如下:
这个程式是我看malloc_consolidate时的一个知识点验证程式,并不是说有多实用,多精巧 :)
关于fastbins理解如下:
fastbins的概念就是小于av->max_fast
(最大为80,默认为64,能通过mallopt指定)的小堆如果每次free时都进行和周围空块的合并工作并不能有效增大空块的大小而且非常可能紧 接着会再次出现小空间的malloc请求,作为折中每次free这些小堆时并不进行合并工作, 而是将他们记录到av->fastbins里边,按大小组成多个等大小的单链表,每个链表头就在
fastbins[]数组中;当有堆malloc请求或free后空快的size >0xffff时就调用 malloc_consolidate进行一次清理工作:将fastbins里的各个链表遍历一遍,对每个链表上的块进行和周围空闲块的合并工作(如果前一个块为空闲则unlink前块,如果后一个块是空 闲则unlink后一个块),合并后将整个块放到正常的unsorted_chunks中形成正常的空闲块, 并从fastbins的链表里删除。
在glibc中,释放内存是使用sYSTRIm(size_t pad, mstate
av)函数,其说明如下:
/*
sYSTRIm is an inverse of sorts to sYSMALLOc. It gives
memory back
to the system (via negative arguments to sbrk) if there is unused
memory at the `high’ end of the malloc pool. It is called
automatically by free() when top space exceeds the trim
threshold. It is also called by the public malloc_trim
routine. It
returns 1 if it actually released any memory, else 0.
*/
下面我们再做一个试验,就能非常清晰的看到glibc库是在什么时候释放内存。
#include
#include
#include
int main(void)
{
char *p[1000];
int i=0;
for(i=0;i=0;i--)
{
free(p);
}
}
strace的结果:
brk(0)
= 0x
brk(0x9516000)
= 0x9516000
brk(0)
=
0x9516000
brk(0)
=
0x9516000
brk(0x9537000)
= 0x9537000
brk(0)
=
0x9537000
brk(0x9558000)
= 0x9558000
brk(0)
=
0x9558000
brk(0x9579000)
= 0x9579000
brk(0)
=
0x9579000
brk(0x
brk(0)
= 0x
brk(0x95bb000)
= 0x95bb000
brk(0)
=
0x95bb000
brk(0x95dc000)
= 0x95dc000
brk(0)
=
0x95dc000
brk(0x95fd000)
= 0x95fd000
brk(0)
=
0x95fd000
brk(0)
=
0x95fd000
brk(0x95fc000)
= 0x95fc000
brk(0)
=
0x95fc000
brk(0)
=
0x95fc000
brk(0)
=
0x95fc000
brk(0x95fb000)
= 0x95fb000
brk(0)
=
0x95fb000
brk(0)
=
0x95fb000
brk(0)
=
0x95fb000
brk(0x95fa000)
= 0x95fa000
brk(0)
=
0x95fa000
brk(0)
=
0x95fa000
brk(0)
=
0x95fa000
brk(0x
brk(0)
= 0x
brk(0)
= 0x
brk(0)
= 0x
brk(0x
brk(0)
= 0x
brk(0)
= 0x
brk(0)
= 0x
后面更有非常多,这里就能非常清晰的看到,每当堆的最后空闲出0x1000时,就开始释放堆栈,也有可能粒度更小,能修改程式进行试验。
那么接下来更有一个问题,如果前面的内存都已free掉了,只是最后的内容没有释放掉,那么以前free的内存其对应的页面是否释放?
能做一个试验来测试一下。
试验1:
#include
#include
#include
#include
int main (void)
{
int pid=getpid();
int i=1;
char *p[1000];
printf("%d pid,hello world\n",pid);
for(i=0;i
#include
#include
#include
int main (void)
{
int pid=getpid();
int i=1;
char *p[1000];
printf("%d pid,hello world\n",pid);
for(i=0;i
#include
#include
#include
int main (void)
{
int pid=getpid();
int i=1;
char *p[1000];
printf("%d pid,hello world\n",pid);
for(i=0;i所以glibc在进行堆栈管理的时候,如果位于堆顶端的不释放,那么整个堆栈所占用的物理空间都不会释放。但前面释放出来的空间,能为新的内存申请提供内存。