关于glibc中内存回收的试验

来源:互联网 发布:阿里云副总裁名单 编辑:程序博客网 时间:2024/05/23 00:08
零零散散的看了一些glibc堆管理的内部机制,大致清楚了,但是堆管理器是如何将释放的内存返回给系统的。在前面的文章中提到了,glibc的堆管理器在堆尾部空闲地址大于threshold时,就会返回给kernel,但一直没有找到。通过使用strace跟踪,也一直没有跟踪到系统释放内存,很郁闷。
 
如果不释放的话,忽然想做一个试验,通过strace来进行跟踪:
试验一:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
    char *p[1000];
    int i=0;
    for(i=0;i<1000;i++)
    {
        p[i] =(char *) malloc(1024);
    }
    for(i=0;i<1000;i++)
    {
        free(p[i]);
    }
}
 
strace日志如下:
前面有些调用在此省略掉了。
brk(0)                                  = 0x890f000
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(0x89f6000)                          = 0x89f6000
brk(0)                                  = 0x89f6000
brk(0x8a17000)                          = 0x8a17000
brk(0)                                  = 0x8a17000
brk(0)                                  = 0x8a17000
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main(void)
{
    char *p[1000];
    int i=0;
    for(i=0;i<1000;i++)
    {
        p[i] =(char *) malloc(1024);
    }
    for(i=999;i>=0;i--)
    {
        free(p[i]);
    }
}
strace的结果:
brk(0)                                  = 0x94f5000
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(0x959a000)                          = 0x959a000
brk(0)                                  = 0x959a000
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(0x95f9000)                          = 0x95f9000
brk(0)                                  = 0x95f9000
brk(0)                                  = 0x95f9000
brk(0)                                  = 0x95f9000
brk(0x95f8000)                          = 0x95f8000
brk(0)                                  = 0x95f8000
brk(0)                                  = 0x95f8000
brk(0)                                  = 0x95f8000
后面还有很多,这里就可以很清楚的看到,每当堆的最后空闲出0x1000时,就开始释放堆栈,也有可能粒度更小,可以修改程序进行试验。
 
那么接下来还有一个问题,如果前面的内存都已经free掉了,只是最后的内容没有释放掉,那么以前free的内存其对应的页面是否释放?
可以做一个试验来测试一下。
试验1:
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
                                                                          
int main (void)
{
   int pid=getpid();
   int i=1;
   char *p[1000];
   printf("%d pid,hello world\n",pid);
   for(i=0;i<1000;i++)
   {
      p[i]=(char*)malloc(1024);
      strcpy(p[i],"123");
   }
   sleep(1000);
   return 0;
}
通过/proc/pid/statm查看
339 338 73 2 2 334 265
338个物理页面
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
                                                                          
int main (void)
{
   int pid=getpid();
   int i=1;
   char *p[1000];
   printf("%d pid,hello world\n",pid);
   for(i=0;i<1000;i++)
   {
      p[i]=(char*)malloc(1024);
      strcpy(p[i],"123");
   }
   for(i=0;i<999;i++)
   {
      free(p[i]);
   }
   sleep(1000);
   return 0;
}
剩余一个页面的时候
339 338 73 2 2 334 265
物理页面没有变化。
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
                                                                          
int main (void)
{
   int pid=getpid();
   int i=1;
   char *p[1000];
   printf("%d pid,hello world\n",pid);
   for(i=0;i<1000;i++)
   {
      p[i]=(char*)malloc(1024);
      strcpy(p[i],"123");
   }
   for(i=0;i<1000;i++)
   {
      free(p[i]);
   }
   sleep(1000);
   return 0;
}
这时就可以很清楚的看到其物理页面也进行了释放。
119 118 73 2 2 114 45
所以glibc在进行堆栈管理的时候,如果位于堆顶端的不释放,那么整个堆栈所占用的物理空间都不会释放。但前面释放出来的空间,可以为新的内存申请提供内存。

原创粉丝点击