Public_fREe()
void public_fREe(Void_t* mem)
{
mstate ar_ptr; mchunkptr p;
/* chunk corresponding to mem */
void (*hook) (__malloc_ptr_t, __const __malloc_ptr_t)
= force_reg (__free_hook);
if (__builtin_expect (hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS (0));
return;
}
如果存在__free_hook,执行hook函数。
if (mem == 0)
/* free(0) has no effect */
return;
p = mem2chunk(mem);
free NULL指针直接返回,然后根据内存指针获得chunk的指针。
#if HAVE_MMAP
if (chunk_is_mmapped(p))
/* release mmapped memory. */
{
/* see if the dynamic brk/mmap threshold needs adjusting */
if (!mp_.no_dyn_threshold
&& p->size > mp_.mmap_threshold
&& p->size <= DEFAULT_MMAP_THRESHOLD_MAX)
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
}
munmap_chunk(p);
return;
}
#endif
如果当前free的chunk是通过mmap()分配的,调用munmap_chunk()函数unmap本chunk。 munmap_chunk()函数调用 munmap()函数释放mmap()分配的内存块。同时查看是否开启了mmap分配阈值动态调整机制,默认是开启的,如果当前free的chunk的大小大于设置的mmap分配阈值,小于mmap分配阈值的最大值,将当前chunk的大小赋值给mmap分配阈值,并修改mmap收缩阈值为mmap分配阈值的2倍。默认情况下mmap分配阈值与mmap收缩阈值相等,都为128KB。
ar_ptr = arena_for_chunk(p);
根据chunk指针获得分配区的指针。
#ifdef ATOMIC_FASTBINS
_int_free(ar_ptr, p, 0);
如果开启了ATOMIC_FASTBINS优化,不需要对分配区加锁,调用_int_free()函数执行实际的释放工作。
#else
#if THREAD_STATS
if(!mutex_trylock(&ar_ptr->mutex))
++(ar_ptr->stat_lock_direct);
else
{
(void)mutex_lock(&ar_ptr->mutex);
++(ar_ptr->stat_lock_wait);
}
#else
(void)mutex_lock(&ar_ptr->mutex);
#endif
_int_free(ar_ptr, p);
(void)mutex_unlock(&ar_ptr->mutex);
#endif
}
如果没有开启了ATOMIC_FASTBINS优化,或去分配区的锁,调用_int_free()函数执行实际的释放工作,然后对分配区解锁。
_int_free()
static void
#ifdef ATOMIC_FASTBINS
_int_free(mstate av, mchunkptr p, int have_lock)
#else
_int_free(mstate av, mchunkptr p)
#endif
{
INTERNAL_SIZE_T size; /* its size */
mfastbinptr* fb; /* associated fastbin */
mchunkptr nextchunk; /* next contiguous chunk */
INTERNAL_SIZE_T nextsize; /* its size */
int nextinuse; /* true if nextchunk is used */
INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */
mchunkptr bck; /* misc temp for linking */
mchunkptr fwd; /* misc temp for linking */
const char *errstr = NULL;
#ifdef ATOMIC_FASTBINS
int locked = 0;
#endif
size = chunksize(p);
获取需要释放的chunk的大小。
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
{
errstr = "free(): invalid pointer";
errout:
#ifdef ATOMIC_FASTBINS
if (! have_lock && locked)
(void)mutex_unlock(&av->mutex);
#endif
malloc_printerr (check_action, errstr, chunk2mem(p));
return;
}
/* We know that each chunk is at least MINSIZE bytes in size. */
if (__builtin_expect (size < MINSIZE, 0))
{
errstr = "free(): invalid size";
goto errout;
}
check_inuse_chunk(av, p);
上面的代码用于安全检查,chunk的指针地址不能溢出,chunk的大小必须大于等于MINSIZE。
/*
If eligible, place chunk on a fastbin so it can be found
and used quickly in malloc.
*/
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
#if TRIM_FASTBINS
/*
If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins
*/
&& (chunk_at_offset(p, size) != av->top)
#endif)
{
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
{
#ifdef ATOMIC_FASTBINS
/* We might not have a lock at this point and concurrent modifications
of system_mem might have let to a false positive. Redo the test
after getting the lock. */
if (have_lock
|| ({ assert (locked == 0);
mutex_lock(&av->mutex);
locked = 1;
chunk_at_offset (p, size)->size <= 2 * SIZE_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem;
}))
#endif
{
errstr = "free(): invalid next size (fast)";
goto errout;
}
#ifdef ATOMIC_FASTBINS
if (! have_lock)
{
(void)mutex_unlock(&av->mutex);
locked = 0;
}
#endif
}
如果当前free的chunk属于fast bins,查看下一个相邻的chunk的大小是否小于等于2*SIZE_SZ,下一个相邻chunk的大小是否大于分配区所分配的内存总量,如果是,报错。
if (__builtin_expect (perturb_byte, 0))
free_perturb (chunk2mem(p), size - SIZE_SZ);
set_fastchunks(av);
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);
设置当前分配区的fast bin flag,表示当前分配区的fast bins中已有空闲chunk。然后根据当前free的chunk大小获取所属的fast bin。
#ifdef ATOMIC_FASTBINS
mchunkptr fd;
mchunkptr old = *fb;
unsigned int old_idx = ~0u;
do
{
/* Another simple check: make sure the top of the bin is not the
record we are going to add (i.e., double free). */
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
if (old != NULL)
old_idx = fastbin_index(chunksize(old));
p->fd = fd = old;
}while ((old = catomic_compare_and_exchange_val_rel (fb, p, fd)) != fd);
if (fd != NULL && __builtin_expect (old_idx != idx, 0))
{
errstr = "invalid fastbin entry (free)";
goto errout;
}
如果开启了ATOMIC_FASTBINS优化,使用lock-free技术实现fast bin的单向链表插入操作。
#else
if (__builtin_expect (*fb == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
if (*fb != NULL
&& __builtin_expect (fastbin_index(chunksize(*fb)) != idx, 0))
{
errstr = "invalid fastbin entry (free)";
goto errout;
}
p->fd = *fb; *fb = p
#endif
}
如果没有开启了ATOMIC_FASTBINS优化,将free的chunk加入fast bin的单向链表中, 修改过链表表头为当前free的chunk。同时需要校验是否为double free错误,校验表头不为NULL情况下,保证表头chunk的所属的fast bin与当前free的chunk所属的fast bin相同。
else if (!chunk_is_mmapped(p))
{
#ifdef ATOMIC_FASTBINS
if (! have_lock)
{
# if THREAD_STATS
if(!mutex_trylock(&av->mutex))
++(av->stat_lock_direct);
else
{
(void)mutex_lock(&av->mutex);
++(av->stat_lock_wait);
}
# else
(void)mutex_lock(&av->mutex);
# endif
locked = 1;
}
#endif
如果当前 free 的 chunk 不是通过 mmap()分配的,并且当前还没有获得分配区的锁,获取分配区的锁。
nextchunk = chunk_at_offset(p, size);
获取当前 free 的 chunk 的下一个相邻的 chunk。
if (__builtin_expect (p == av->top, 0))
{
errstr = "double free or corruption (top)";
goto errout;
}
/* Or whether the next chunk is beyond the boundaries of the arena. */
if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
{
errstr = "double free or corruption (out)";
goto errout;
}
/* Or whether the block is actually not marked used. */
if (__builtin_expect (!prev_inuse(nextchunk), 0))
{
errstr = "double free or corruption (!prev)";
goto errout;
}
安全检查,当前 free 的 chunk 不能为 top chunk,因为 top chunk 为空闲 chunk,如果再次 free 就可能为double free 错误了。如果当前 free 的 chunk 是通过 sbrk()分配的,并且下一个相邻的 chunk 的地址已经超过了 top chunk 的结束地址,超过了当前分配区的结束地址,报错。如果当前 free 的 chunk 的下一个相邻 chunk
的 size 中标志位没有标识当前 free chunk 为 inuse 状态,可能为 double free 错误。
nextsize = chunksize(nextchunk);
if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0))
{
errstr = "free(): invalid next size (normal)";
goto errout;
}
if (__builtin_expect (perturb_byte, 0))
free_perturb (chunk2mem(p), size - SIZE_SZ);
计算当前 free 的 chunk 的下一个相邻 chunk 的大小,该大小如果小于等于 2*SIZE_SZ 或是大于了分配区所分配区的内存总量,报错。
/* consolidate backward */
if (!prev_inuse(p))
{
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(p, bck, fwd);
}
如果当前 free 的 chunk 的前一个相邻 chunk 为空闲状态,与前一个空闲 chunk 合并。计算合并后的 chunk 大小,并将前一个相邻空闲 chunk 从空闲 chunk 链表中删除。
if (nextchunk != av->top)
{
/* get and clear inuse bit */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
如果与当前 free 的 chunk 相邻的下一个 chunk 不是分配区的 top chunk,查看与当前 chunk 相邻的下一个 chunk 是否处于 inuse 状态。
/* consolidate forward */
if (!nextinuse)
{
unlink(nextchunk, bck, fwd);
size += nextsize;
}
else
clear_inuse_bit_at_offset(nextchunk, 0);
如果与当前 free 的 chunk 相邻的下一个 chunk 处于 inuse 状态,清除当前 chunk 的 inuse 状态,则当前 chunk 空闲了。否则,将相邻的下一个空闲 chunk 从空闲链表中删除,并计算当前 chunk 与下一个 chunk 合并后的 chunk 大小。
bck = unsorted_chunks(av);
fwd = bck->fd;
if (__builtin_expect (fwd->bk != bck, 0))
{
errstr = "free(): corrupted unsorted chunks";
goto errout;
}
p->fd = fwd;
p->bk = bck;
if (!in_smallbin_range(size))
{
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
bck->fd = p;
fwd->bk = p;
将合并后的 chunk 加入 unsorted bin 的双向循环链表中。如果合并后的 chunk 属于 large bins,将 chunk 的 fd_nextsize 和 bk_nextsize 设置为 NULL。
set_head(p, size | PREV_INUSE);
set_foot(p, size);
设置合并后的空闲 chunk 大小,并标识前一个 chunk 处于 inuse 状态,因为必须保证不能有两个相邻的 chunk 都处于空闲状态。然后将合并后的 chunk 加入 unsorted bin 的双向循环链表中。最后设置合并后的空闲 chunk 的 foot,chunk 空闲时必须设置 foot,该 foot 处于下一个 chunk 的 prev_size 中,只有 chunk 空闲是 foot 才是有效的。
check_free_chunk(av, p);
}
/*
If the chunk borders the current high end of memory,
consolidate into top
*/
else
{
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
check_chunk(av, p);
}
如果当前 free 的 chunk 下一个相邻的 chunk 为 top chunk,则将当前 chunk 合并入 top chunk,修改 top chunk 的大小。
if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD)
{
if (have_fastchunks(av))
malloc_consolidate(av);
如果合并后的 chunk 大小大于 64KB,并且 fast bins 中存在空闲 chunk,调用 malloc_consolidate()函数合并 fast bins 中的空闲 chunk 到 unsorted bin 中。
if (av == &main_arena)
{
#ifndef MORECORE_CANNOT_TRIM
if ((unsigned long)(chunksize(av->top)) >=
(unsigned long)(mp_.trim_threshold))
sYSTRIm(mp_.top_pad, av);
#endif
如果当前分配区为主分配区,并且top chunk的大小大于heap的收缩阈值,调用sYSTRIm() 函数首先 heap。
else
{
/* Always try heap_trim(), even if the top chunk is not
large, because the correspo nding heap might go away. */
heap_info *heap = heap_for_ptr(top(av));
assert(heap->ar_ptr == av);
heap_trim(heap, mp_.top_pad);
}
}
如果为非主分配区,调用 heap_trim()函数收缩非主分配区的 sub_heap。
#ifdef ATOMIC_FASTBINS
if (! have_lock)
{
assert (locked);
(void)mutex_unlock(&av->mutex);
}
}
#endif
如果开启了 ATOMIC_FASTBINS 优化并获得分配区的锁,则对分配区解锁。
else
{
#if HAVE_MMAP
munmap_chunk (p);
#endif
}
}
如果当前 free 的 chunk 是通过 mmap()分配的,调用 munma_chunk()释放内存。
check
1.释放chunk的时候,chunk必须按2size_sz对齐,chunk1的大小必须大于等于MINSIZE且指针地址不能溢出。
2.释放fast bin的时候,chunk必须大于2SIZE_SZ且小于分配区所分配的内存总量。
3.释放fast bin时,检查当前free的chunk是否是fast bin中的链表头(double free),以及当前free的chunk的size要与相应的fast bin一致。
4.释放chunk的时候,chunk不能为top chunk,next chunk的地址不能超过当前分配区结束的地址,以及next chunk中chunk的inuse标志位需置1。
5.当前 free 的 chunk 的下一个相邻 chunk 的大小需要大于 2*SIZE_SZ 且小于分配区所分配区的内存总量。
6.释放的chunk通过unlink脱链,注意unlink的检查。
7.将free掉的chunk放入unsorted bin中时,有unsorted_chunks(av)->fd->bk == unsorted_chunks(av)的检查。
总结
free大致步骤:
1.判断是否有_free_hook,有则执行hook函数。
2.判断是否是mmap chunk,是则调用munmap_chunk释放(同时可能做调整阈值操作),否则调用_int_free()。
3.判断是否是fast bin,是则插入fast bin链表中(inuse值不置0)。
4.如果不是mmap chunk,判断相邻chunk是否为空闲,是就合并(top chunk除外),将合并后的chunk插入unsorted bin链表中。
5.如果跟top chunk相邻,则合并入top chunk。
6.依据heap的情况可能执行合并fast bin、收缩阈值以及收缩sub_heap等操作。
7.判断是否还存在mmap chunk,是则调用munmap_chunk释放。
内容来源
《glibc内存管理ptmalloc源码分析》