Glibc内存管理--ptmalloc2源代码分析(二十四)

5.6.7 grow_heap,shrink_heap,delete_heap,heap_trim

几个函数实现 sub_heap 和增长和收缩, grow_heap() 函数主要将 sub_heap 中可读可写区域扩大; shrink_heap() 函数缩小 sub_heap 的虚拟内存区域,减小该 sub_heap 的虚拟内存占用量; delete_heap() 为一个宏,如果 sub_heap 中所有的内存都空闲,使用该宏函数将 sub_heap 的虚拟内存还回给操作系统; heap_trim() 函数根据 sub_heap top chunk 大小调用 shrink_heap() 函数收缩 sub_heap

函数的实现代码如下:

static int
#if __STD_C
grow_heap(heap_info *h, long diff)
#else
grow_heap(h, diff) heap_info *h; long diff;
#endif
{
  size_t page_mask = malloc_getpagesize - 1;
  long new_size;

  diff = (diff + page_mask) & ~page_mask;
  new_size = (long)h->size + diff;
  if((unsigned long) new_size > (unsigned long) HEAP_MAX_SIZE)
    return -1;
  if((unsigned long) new_size > h->mprotect_size) {
    if (mprotect((char *)h + h->mprotect_size,
                 (unsigned long) new_size - h->mprotect_size,
                 PROT_READ|PROT_WRITE) != 0)
      return -2;
    h->mprotect_size = new_size;
  }

  h->size = new_size;
  return 0;
}

 Grow_heap() 函数的实现比较简单,首先将要增加的可读可写的内存大小按照页对齐,然后计算 sub_heap 总的可读可写的内存大小 new_size ,判断 new_size 是否大于 HEAP_MAX_SIZE ,如果是,返回,否则判断 new_size 是否大于当前 sub_heap 的可读可写区域大小,如果否,调用 mprotect() 设置新增的区域可读可写,并更新当前 sub_heap 的可读可写区域的大小为 new_size 。最后将当前 sub_heap 的字段 size 更新为 new_size

Shrink_heap() 函数的实现源代码如下:

static int
#if __STD_C
shrink_heap(heap_info *h, long diff)
#else
shrink_heap(h, diff) heap_info *h; long diff;
#endif
{
  long new_size;

  new_size = (long)h->size - diff;
  if(new_size < (long)sizeof(*h))
    return -1;
  /* Try to re-map the extra heap space freshly to save memory, and
     make it inaccessible. */
#ifdef _LIBC
  if (__builtin_expect (__libc_enable_secure, 0))
#else
  if (1)
#endif
    {
      if((char *)MMAP((char *)h + new_size, diff, PROT_NONE,
                      MAP_PRIVATE|MAP_FIXED) == (char *) MAP_FAILED)
        return -2;
      h->mprotect_size = new_size;
    }
#ifdef _LIBC
  else
    madvise ((char *)h + new_size, diff, MADV_DONTNEED);
#endif
  /*fprintf(stderr, "shrink %p %08lx\n", h, new_size);*/

  h->size = new_size;
  return 0;
}

 Shrink_heap() 函数的参数 diff 已经页对齐,同时 sub_heap size 也是安装页对齐的,所以计算 sub_heap new_size 时不用再处理页对齐。如果 new_size sub_heap 的首地址还小,报错退出,如果该函数运行在非 Glibc 中,则从 sub_heap 中切割出 diff 大小的虚拟内存,创建一个新的不可读写的映射区域,注意 mmap() 函数这里使用了 MAP_FIXED 标志,然后更新 sub_heap 的可读可写内存大小。如果该函数运行在 Glibc 库中,则调用 madvise() 函数,实际上 madvise() 函数什么也不做,只是返回错误,这里并没有处理 madvise() 函数的返回值。

#define delete_heap(heap) \
  do {                                                          \
    if ((char *)(heap) + HEAP_MAX_SIZE == aligned_heap_area)    \
      aligned_heap_area = NULL;                                 \
    munmap((char*)(heap), HEAP_MAX_SIZE);                       \
  } while (0)

 Delete_heap() 宏函数首先判断当前删除的 sub_heap 的结束地址是否与全局变量 aligned_heap_area 指向的地址相同,如果相同,则将全局变量 aligned_heap_area 设置为 NULL ,因为当前 sub_heap 删除以后,就可以从当前 sub_heap 的起始地址或是更低的地址开始映射新的 sub_heap ,这样可以尽量从地地址映射内存。然后调用 munmap() 函数将整个 sub_heap 的虚拟内存区域释放掉。在调用 munmap() 函数时, heap_trim() 函数调用 shrink_heap() 函数可能已将 sub_heap 切分成多个子区域, munmap() 函数的第二个参数为 HEAP_MAX_SIZE ,无论该 sub_heap (大小为 HEAP_MAX_SIZE )的内存区域被切分成多少个子区域,将整个 sub_heap 都释放掉了。

Heap_trim() 函数的源代码如下:

static int
internal_function
#if __STD_C
heap_trim(heap_info *heap, size_t pad)
#else
heap_trim(heap, pad) heap_info *heap; size_t pad;
#endif
{
  mstate ar_ptr = heap->ar_ptr;
  unsigned long pagesz = mp_.pagesize;
  mchunkptr top_chunk = top(ar_ptr), p, bck, fwd;
  heap_info *prev_heap;
  long new_size, top_size, extra;

  /* Can this heap go away completely? */
  while(top_chunk == chunk_at_offset(heap, sizeof(*heap))) {

 每个非主分配区至少有一个 sub_heap ,每个非主分配区的第一个 sub_heap 中包含了一个 heap_info 的实例和 malloc_state 的实例,分主分配区中的其它 sub_heap 中只有一个 heap_info 实例,紧跟 heap_info 实例后,为可以用于分配的内存块。当当前非主分配区的 top chunk 与当前 sub_heap heap_info 实例的结束地址相同时,意味着当前 sub_heap 中只有一个空闲 chunk ,没有已分配的 chunk 。所以可以将当前整个 sub_heap 都释放掉。

prev_heap = heap->prev;
    p = chunk_at_offset(prev_heap, prev_heap->size - (MINSIZE-2*SIZE_SZ));
    assert(p->size == (0|PREV_INUSE)); /* must be fencepost */
    p = prev_chunk(p);
    new_size = chunksize(p) + (MINSIZE-2*SIZE_SZ);
    assert(new_size>0 && new_size<(long)(2*MINSIZE));
    if(!prev_inuse(p))
      new_size += p->prev_size;
    assert(new_size>0 && new_size<HEAP_MAX_SIZE);
    if(new_size + (HEAP_MAX_SIZE - prev_heap->size) < pad + MINSIZE + pagesz)
      break;

 每个sub_heap 的可读可写区域的末尾都有两个 chunk 用于 fencepost ,以 64 位系统为例,最后一个 chunk 占用的空间为 MINSIZE-2 *SIZE_SZ ,为 16B ,最后一个 chuk size 字段记录的前一个 chunk inuse 状态,并标识当前 chunk 大小为 0 ,倒数第二个 chunk inuse 状态,这个 chunk 也是 fencepost 的一部分,这个 chunk 的大小为 2 *SIZE_SZ ,为 16B ,所以用于 fencepost 的两个 chunk 的空间大小为 32B fencepost 也有可能大于 32B ,第二个 chunk 仍然为 16B ,第一个 chunk 的大小大于 16B ,这种情况发生在 top chunk 的空间小于 2*MINSIZE ,大于 MINSIZE ,但对于一个完全空闲的 sub_heap 来说, top chunk 的空间肯定大于 2*MINSIZE ,所以在这里不考虑这种情况。用于 fencepost chunk 空间其实都是被分配给应用层使用的, new_size 表示当前 sub_heap 中可读可写区域的可用空间,如果倒数第二个 chunk 的前一个 chunk 为空闲状态,当前 sub_heap 中可读可写区域的可用空间大小还需要加上这个空闲 chunk 的大小。如果 new_size sub_heap 中剩余的不可读写的区域大小之和小于 32+4K 64 位系统),意味着前一个 sub_heap 的可用空间太少了,不能释放当前的 sub_heap

ar_ptr->system_mem -= heap->size;
    arena_mem -= heap->size;
    delete_heap(heap);
    heap = prev_heap;
    if(!prev_inuse(p)) { /* consolidate backward */
      p = prev_chunk(p);
      unlink(p, bck, fwd);
    }
    assert(((unsigned long)((char*)p + new_size) & (pagesz-1)) == 0);
    assert( ((char*)p + new_size) == ((char*)heap + heap->size) );
    top(ar_ptr) = top_chunk = p;
    set_head(top_chunk, new_size | PREV_INUSE);
/*check_chunk(ar_ptr, top_chunk);*/

 首先更新非主分配区的内存统计,然后调用 delete_heap() 宏函数释放该 sub_heap ,把当前 heap 设置为被释放 sub_heap 的前一个 sub_heap p 指向的是被释放 sub_heap 的前一个 sub_heap 的倒数第二个 chunk ,如果 p 的前一个 chunk 为空闲状态,由于不可能出现多个连续的空闲 chunk ,所以将 p 设置为 p 的前一个 chunk ,也就是 p 指向空闲 chunk ,并将该空闲 chunk 从空闲 chunk 链表中移除,并将将该空闲 chunk 赋值给 sub_heap top chunk ,并设置 top chunk size ,标识 top chunk 的前一个 chunk 处于 inuse 状态。然后继续判断循环条件,如果循环条件不满足,退出循环,如果条件满足,继续对当前 sub_heap 进行收缩。

 }
  top_size = chunksize(top_chunk);
  extra = ((top_size - pad - MINSIZE + (pagesz-1))/pagesz - 1) * pagesz;
  if(extra < (long)pagesz)
    return 0;
  /* Try to shrink. */
  if(shrink_heap(heap, extra) != 0)
    return 0;
  ar_ptr->system_mem -= extra;
  arena_mem -= extra;

  /* Success. Adjust top accordingly. */
  set_head(top_chunk, (top_size - extra) | PREV_INUSE);
  /*check_chunk(ar_ptr, top_chunk);*/

 首先查看 top chunk 的大小,如果 top chunk 的大小减去 pad MINSIZE 小于一页大小,返回退出,否则调用 shrink_heap() 函数对当前 sub_heap 进行收缩,将空闲的整数个页收缩掉,仅剩下不足一页的空闲内存,如果 shrink_heap() 失败,返回退出,否则,更新内存使用统计,更新 top chunk 的大小。

  return 1;
}
 

猜你喜欢

转载自mqzhuang.iteye.com/blog/1064929