/* size field is or'ed with PREV_INUSE when provious adjacent chunk in use */ #define PREV_INUSE 0x1 /* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */ #define IS_MMAPPED 0x2 /* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained from a non-main arena .*/ #define NON_MAIN_ARENA 0x4
/* Bits to mask off when extracting size. */ #define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
当一个非 fast bin 的 chunk 被释放,会与相邻的 chunk 进行合并,顺序通常是先向后(上)合并再向前(下)合并。如果向前合并的 chunk 是 top chunk ,则合并之后会形成新的 top chunk ,则合成之后会形成新的 top chunk ;如果不是的话,则合并之后会被加入 unsorted bin 中。
binmap 结构在索引 bin 的时候使用,binmap 为 malloc_state 的成员,其中一个比特位表示 bins 中相应的 bin 状态,1表示 bin 不为空,0 表示为空,这样能加快搜索速度。
3.malloc_consolidate()函数:
由于 fast bin 中的 chunk 永远不会释放,导致相邻的 free chunk 无法与之合并,从而造成了大量的内存碎片,malloc_consolidate() 函数主要功能就是来解决这个问题。在达到某些条件时,glibc 就会调用该函数将 fast bin 中的 chunk 取出来,与相邻的 free chunk 合并之后放入 unsorted bin ,或者与 top chunk 合并后形成新的 top chunk 。
if (in_smallbin_range (nb)) { idx = smallbin_index (nb); bin = bin_at (av, idx);
if ((victim = last (bin)) != bin) { bck = victim->bk; if (__glibc_unlikely (bck->fd != victim)) //类似unlink检查 malloc_printerr ("malloc(): smallbin double linked list corrupted"); set_inuse_bit_at_offset (victim, nb); bin->bk = bck; bck->fd = bin;
if (av != &main_arena) set_non_main_arena (victim); check_malloced_chunk (av, victim, nb); #if USE_TCACHE /* While we're here, if we see other chunks of the same size, stash them in the tcache. */ size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */ while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin)) != bin) { if (tc_victim != 0) { bck = tc_victim->bk; set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); bin->bk = bck; bck->fd = bin;
(5)接下来,函数进入一个大的外层循环 for 循环,包含了 _int_malloc() 函数之后的所有进程。紧接着是内层第一个 while 循环,它会遍历 unsorted bin 中的每一个 chunk ,如果大小正好合适,就将其取出,否则就将其放入 small bin 或者 large bin。这是唯一的将 chunk 放进 small bin 或者 large bin 的过程。
#if USE_TCACHE /* If we've processed as many chunks as we're allowed while filling the cache, return one of the cached ones. */ ++tcache_unsorted_count; if (return_cached && mp_.tcache_unsorted_limit > 0 && tcache_unsorted_count > mp_.tcache_unsorted_limit) { return tcache_get (tc_idx); } #endif
(9)如果用户申请的 chunk 是 large chunk ,就在第一个循环结束后搜索 large bin 。
/* If a large request, scan through the chunks of current bin in sorted order to find smallest that fits. Use the skip list for this. */
if (!in_smallbin_range (nb)) //申请的chunk 在large bin范围内 { bin = bin_at (av, idx);
/* skip scan if empty or largest chunk is too small */ //如果victim等于头节点,说明bin为空,如果小于nb说明不会有合适的 if ((victim = first (bin)) != bin && (unsignedlong) chunksize_nomask (victim) >= (unsignedlong) (nb)) { //反向遍历chunk,找到第一个不小于nb的chunk victim = victim->bk_nextsize; while (((unsignedlong) (size = chunksize (victim)) < (unsignedlong) (nb))) victim = victim->bk_nextsize; /* Avoid removing the first entry for a size so that the skip list does not have to be rerouted. */ //如果该chunk和他的fd chunk 一样大,就选择fd chunk,避免改动nextsize if (victim != last (bin) && chunksize_nomask (victim) == chunksize_nomask (victim->fd)) victim = victim->fd;
for (;; ) { /*binmap数组的元素类型是unsigned int,32和64位一般都是4字节,也就是32比特,所以binmap的一个block能够检查32个bin*/ /* Skip rest of block if there are no more set bits in this block. */ if (bit > map || bit == 0) { do { if (++block >= BINMAPSIZE) /* out of bins *///如果成立,说明遍历了所有的bin且都为空,只有区top chunk中寻找 goto use_top; } while ((map = av->binmap[block]) == 0);
bin = bin_at (av, (block << BINMAPSHIFT)); bit = 1; }
/* Advance to bin with set bit. There must be one. */ //找到block不为空的最小的bin对应的bit while ((bit & map) == 0) { bin = next_bin (bin); bit <<= 1; assert (bit != 0); }
(12)然后检查 bit 对应的 bin 是否为空,如果是,就清空对应的 bit 位,从下一个 bin 开始循环,否则将 Victim 从 bin 中取出来。
/* Inspect the bin. It is likely to be non-empty */ victim = last (bin);
/* If a false alarm (empty bin), clear the bit. */ if (victim == bin) { av->binmap[block] = map &= ~bit; /* Write through */ bin = next_bin (bin); bit <<= 1; }
else { size = chunksize (victim);
/* We know the first chunk in this bin is big enough to use. */ assert ((unsignedlong) (size) >= (unsignedlong) (nb));
remainder_size = size - nb;
/* unlink */ unlink_chunk (av, victim);
(13)将取出的victim进行切分,并把 remainder 加入 unsorted bin ,如果 victim 不够切分,就直接返回给用户,内层第二个循环到此结束。
use_top: /* If large enough, split off the chunk bordering the end of memory (held in av->top). Note that this is in accord with the best-fit search rule. In effect, av->top is treated as larger (and thus less well fitting) than any other available chunk since it can be extended to be as large as necessary (up to system limitations). We require that av->top always exists (i.e., has size >= MINSIZE) after initialization, so if it would otherwise be exhausted by current request, it is replenished. (The main reason for ensuring it exists is that we may need MINSIZE space to put in fenceposts in sysmalloc.) */
victim = av->top; size = chunksize (victim);
if (__glibc_unlikely (size > av->system_mem)) malloc_printerr ("malloc(): corrupted top size");
/* When we are using atomic ops to free fast chunks we can get here for all block sizes. */ elseif (atomic_load_relaxed (&av->have_fastchunks)) { malloc_consolidate (av); /* restore original bin index */ if (in_smallbin_range (nb)) idx = smallbin_index (nb); else idx = largebin_index (nb); }
staticvoid _int_free (mstate av, mchunkptr p, int have_lock) { 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 */
size = chunksize (p);
/* Little security check which won't hurt performance: the allocator never wrapps around at the end of the address space. Therefore we can exclude some size values which might appear here by accident or by "design" from some intruder. */ //第一个条件筛选掉一些特别大的size,第二个条件检查chunk是否对齐 if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0) || __builtin_expect (misaligned_chunk (p), 0)) malloc_printerr ("free(): invalid pointer"); /* We know that each chunk is at least MINSIZE bytes in size or a multiple of MALLOC_ALIGNMENT. */ //分别检查size是否过小以及size是否对其 if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size))) malloc_printerr ("free(): invalid size");
check_inuse_chunk(av, p); //仅当定义了Malloc_Debug时使用
然后,判断该 chunk 是否在 fast bin 范围内,如果是,则插入 fast bin 中。
//如果size 小于global_max_fast,则将其加入fast bin中 if ((unsignedlong)(size) <= (unsignedlong)(get_max_fast ())
#if TRIM_FASTBINS /* If TRIM_FASTBINS set, don't place chunks bordering top into fastbins */ //TRIM_FASTBINS默认为0,不会将与Top chunk靠近的fast bin删掉 && (chunk_at_offset(p, size) != av->top) #endif ) {
//检查下一个chunk的大小,不能小于2*SIZE,也不能大于av->system_mem if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ, 0) || __builtin_expect (chunksize (chunk_at_offset (p, size)) >= av->system_mem, 0)) { bool fail = true; /* We might not have a lock at this point and concurrent modifications of system_mem might result in a false positive. Redo the test after getting the lock. */ if (!have_lock) { __libc_lock_lock (av->mutex); fail = (chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ || chunksize (chunk_at_offset (p, size)) >= av->system_mem); __libc_lock_unlock (av->mutex); }
if (fail) malloc_printerr ("free(): invalid next size (fast)"); } //释放之前将user data部分填充为 perturb byte free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); //在av中设置位,表示fast bin中有 free chunk atomic_store_relaxed (&av->have_fastchunks, true); unsignedint idx = fastbin_index(size); fb = &fastbin (av, idx); //通过原子操作将p插入fast bin /* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */ mchunkptr old = *fb, old2;
if (SINGLE_THREAD_P) { /* Check that the top of the bin is not the record we are going to add (i.e., double free). */ if (__builtin_expect (old == p, 0)) //简单检查,fast bin中的第一个chunk 是不是刚刚的chunk,防止doubble free malloc_printerr ("double free or corruption (fasttop)"); p->fd = PROTECT_PTR (&p->fd, old); *fb = p; } else do { /* Check that the top of the bin is not the record we are going to add (i.e., double free). */ if (__builtin_expect (old == p, 0)) malloc_printerr ("double free or corruption (fasttop)"); old2 = old; p->fd = PROTECT_PTR (&p->fd, old); } while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2);
/* Check that size of fastbin chunk at the top is the same as size of the chunk that we are adding. We can dereference OLD only if we have the lock, otherwise it might have already been allocated again. */ //再次检查,fast bin if (have_lock && old != NULL && __builtin_expect (fastbin_index (chunksize (old)) != idx, 0)) malloc_printerr ("invalid fastbin entry (free)"); }
如果该 chunk 并非 mmap() 生成的,就需要进行合并,过程如前面所讲,先向后合并,再向前合并。如果合并之后的 chunk 超过了 FASTBIN_CONSOLIDATION_THRESHOLD ,就会整理 fast bin 并向系统返还内存。 _int_free() 函数到此结束。
/* If freeing a large space, consolidate possibly-surrounding chunks. Then, if the total unused topmost memory exceeds trim threshold, ask malloc_trim to reduce top. Unless max_fast is 0, we don't know if there are fastbins bordering top, so we cannot tell for sure whether threshold has been reached unless fastbins are consolidated. But we don't want to consolidate on each free. As a compromise, consolidation is performed if FASTBIN_CONSOLIDATION_THRESHOLD is reached. */
if ((unsignedlong)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) { if (atomic_load_relaxed (&av->have_fastchunks)) malloc_consolidate(av);
if (av == &main_arena) { #ifndef MORECORE_CANNOT_TRIM if ((unsignedlong)(chunksize(av->top)) >= (unsignedlong)(mp_.trim_threshold)) systrim(mp_.top_pad, av); #endif } else { /* Always try heap_trim(), even if the top chunk is not large, because the corresponding heap might go away. */ heap_info *heap = heap_for_ptr(top(av));