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_Cgrow_heap(heap_info *h, long diff)#elsegrow_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_Cshrink_heap(heap_info *h, long diff)#elseshrink_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;}
#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)
static intinternal_function#if __STD_Cheap_trim(heap_info *heap, size_t pad)#elseheap_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,这种情况发生在topchunk的空间小于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的大小,如果topchunk的大小减去pad和MINSIZE小于一页大小,返回退出,否则调用shrink_heap()函数对当前sub_heap进行收缩,将空闲的整数个页收缩掉,仅剩下不足一页的空闲内存,如果shrink_heap()失败,返回退出,否则,更新内存使用统计,更新top chunk的大小。
return 1;}?
?
?