malloc函数详解 glibc2.27
malloc 函数分析(glibc.2.27)
本人菜🐔一只,如果分析的有错误,请大佬指正。
__libc_malloc
函数分析
void * __libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
//把全局变量__malloc_hook赋给了hook,如果hook不为空,则执行hook。
void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
#if USE_TCACHE
size_t tbytes;
checked_request2size (bytes, tbytes);//把bytes转换为tcache bytes
size_t tc_idx = csize2tidx (tbytes);//获取tbytes对应的tcache的tc_idx
MAYBE_INIT_TCACHE ();//如果tcache为NULL,初始化tcache
DIAG_PUSH_NEEDS_COMMENT;
//如果tc_idx合法,且对应的tcache不为空,则直接返回该tcahce中的第一个chunk(与fastbin类似)
if (tc_idx < mp_.tcache_bins
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
#endif
if (SINGLE_THREAD_P)//如果是单线程
{
//调用_int_malloc,实现内存分配
victim = _int_malloc (&main_arena, bytes);
/*
#define arena_for_chunk(ptr) \
(chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)
过以下检测需要满足的要求,只需满足一条即可
1. victim 为 0
2. IS_MMAPPED 为 1
3. NON_MAIN_ARENA 为 0
*/
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
&main_arena == arena_for_chunk (mem2chunk (victim)));
return victim;
}
//获取当前的arena,如果是主线程则获得的是main_arena
arena_get (ar_ptr, bytes);
//调用_int_malloc,实现内存分配
victim = _int_malloc (ar_ptr, bytes);
//如果_int_malloc 分配失败,并且我们之前能够找到一个可用arena,可以用另一个arena重试。
if (!victim && ar_ptr != NULL)
{
LIBC_PROBE (memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
//释放mutex引用的互斥锁对象,因为ptmalloc支持多线程
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
/*
#define arena_for_chunk(ptr) \
(chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)
过以下检测需要满足的要求,只需满足一条即可
1. victim 为 0
2. IS_MMAPPED 为 1
3. NON_MAIN_ARENA 为 0
*/
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
return victim;
}
__int_malloc
函数分析
-
REMOVE_FB的功能是取出空闲chunk
#define REMOVE_FB(fb, victim, pp)//摘除一个空闲chunk do { victim = pp; if (victim == NULL) break; } while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) != victim); //catomic_compare_and_exchange_val_rel_acq 功能是 如果*fb等于victim,则将*fb存储为victim->fd,返回victim; //其作用是从刚刚得到的空闲chunk链表指针中取出第一个空闲的chunk(victim),并将链表头设置为该空闲chunk的下一个chunk(victim->fd)
-
如果需要分配的内存大小nb落在fastbin的范围内,尝试从 fast bins 中 分配 chunk
//get_max_fast返回fastbin可以存储内存的最大值,它在ptmalloc的初始化函数malloc_init_state中定义。 if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) { idx = fastbin_index (nb);//获得chunk大小nb对应的fastbin索引。 mfastbinptr *fb = &fastbin (av, idx);//通过fastbin取出空闲chunk链表头指针 mchunkptr pp; victim = *fb;//获取对应大小的fastbin的链表中的第一个空闲的chunk if (victim != NULL)//如果取到了第一个chunk { if (SINGLE_THREAD_P) //判断是否单线程程序 *fb = victim->fd;//摘除一个空闲chunk else REMOVE_FB (fb, pp, victim);//摘除一个空闲chunk if (__glibc_likely (victim != NULL)) { //由取出的chunk的size计算出来的idx要等于bin的idx //就是检查拿到的chunk的size是否符合该fastbin的大小 size_t victim_idx = fastbin_index (chunksize (victim)); if (__builtin_expect (victim_idx != idx, 0)) malloc_printerr ("malloc(): memory corruption (fast)"); check_remalloced_chunk (av, victim, nb);//什么也没实现 #if USE_TCACHE size_t tc_idx = csize2tidx (nb);//获取对应大小的tcache的idx //如果tchache初始化了,且tc_idx合法 if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim; /* 如果tcache没有装满,且fastbin 中还有空闲的chunk,则把fastbin中的chunk 加入到tcache中,直到tcache满了或者fastbin中不存在chunk */ while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = *fb) != NULL) { if (SINGLE_THREAD_P)//判断是否单线程程序 *fb = tc_victim->fd;//取出一个空闲chunk else { REMOVE_FB (fb, pp, tc_victim);//取出一个空闲chunk if (__glibc_unlikely (tc_victim == NULL)) break; } /* static __always_inline void \ tcache_put (mchunkptr chunk, size_t tc_idx) { tcache_entry *e = (tcache_entry *) chunk2mem (chunk); assert (tc_idx < TCACHE_MAX_BINS); e->next = tcache->entries[tc_idx]; tcache->entries[tc_idx] = e; ++(tcache->counts[tc_idx]); } */ tcache_put (tc_victim, tc_idx);//把fastbin中拿出的chunk加入到tcache链表中 } } #endif void *p = chunk2mem (victim);//把chunk的指针转换成mem的指针 alloc_perturb (p, bytes);//将p的mem部分全部设置为perturb_byte ,默认什么也不做 return p; } } }
-
如果需要分配的内存大小nb落在small bin的范围内,尝试从 small bin中 分配 chunk
if (in_smallbin_range (nb)) { idx = smallbin_index (nb);//获取nb对应大小的small bin的idx bin = bin_at (av, idx);//获取idx对应的small bin if ((victim = last (bin)) != bin)//如果对应的small bin 非空 { bck = victim->bk; //获得vitcm的后一个chunk if (__glibc_unlikely (bck->fd != victim))//检查small bin链表的完整性 malloc_printerr ("malloc(): smallbin double linked list corrupted"); //设置victim物理相邻的下一个chunk的prev_inuse位 set_inuse_bit_at_offset (victim, nb); bin->bk = bck; bck->fd = bin;//把victim从small bin中移除 if (av != &main_arena) set_non_main_arena (victim);//如果不是主线程则设置NON_MAIN_ARENA位 check_malloced_chunk (av, victim, nb);//默认不做任何操作 #if USE_TCACHE size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim; /* 如果tcache没有装满,且small bin 中还有空闲的chunk,则把small bin中的chunk 加入到tcache中,直到tcache满了或者small bin中不存在chunk */ while (tcache->counts[tc_idx] < mp_.tcache_count&& (tc_victim = last (bin)) != bin) { if (tc_victim != 0) { //获得tc_vitcm的后一个chunk bck = tc_victim->bk; //设置tc_victim物理相邻的下一个chunk的prev_inuse位 set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim);//如果不是主线程则设置NON_MAIN_ARENA位 bin->bk = bck; bck->fd = bin; tcache_put (tc_victim, tc_idx);//把small bin中拿出的chunk加入到tcache链表中 } } } #endif void *p = chunk2mem (victim);//把chunk的指针转换成mem的指针 alloc_perturb (p, bytes);//将p的mem部分全部设置为perturb_byte ,默认什么也不做 return p; } }
-
如果我们申请的chunk的大小不在small bin的范围,也就是说我们申请的chunk的大小在large bin的范围,如果fastbin中存在chunk,我们在这一步会遍历fastbin,可以合并的块合并,然后加入到unsorted bin中,如果与topchunk相邻则直接合并到top chunk(注意这里是把fastbin中的所有块清空)
else { idx = largebin_index (nb); if (atomic_load_relaxed (&av->have_fastchunks)) malloc_consolidate (av);//合并fastbin中的空闲chunk } #if USE_TCACHE INTERNAL_SIZE_T tcache_nb = 0; size_t tc_idx = csize2tidx (nb);//获取nb对应大小的tcache的idx if (tcache && tc_idx < mp_.tcache_bins) tcache_nb = nb; int return_cached = 0; tcache_unsorted_count = 0; #endif
-
接着我们遍历unsorted bin,寻找合适的chunk.
//如果unsorted bins不为空,从尾到头遍历unsorted bin中的每个chunk while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0) || __builtin_expect (chunksize_nomask (victim)> av->system_mem, 0)) malloc_printerr ("malloc(): memory corruption"); size = chunksize (victim); /* 如果要申请的大小在smallbin范围 且 unsorted chunks 只有一个chunk,且 victim是last_remainder 且 victim的size大于请求的chunk的大小nb加上 (MINSIZE)最小chunk的size,那么就切割remainder,然后返回victim。 last_remainder 是一个 chunk 指针,分配区上次分配 small chunk 时, 从一个 chunk 中分 裂出一个 small chunk 返回给用户,分裂后的剩余部分 形成一个 chunk,last_remainder 就是 指向的这个 chunk。 */ if (in_smallbin_range (nb) && bck == unsorted_chunks (av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) { //分割remainder remainder_size = size - nb;//计算分割后剩下的size remainder = chunk_at_offset(victim, nb);//获取remainder的地址 //把remainder加入unsorted bin中 unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder; av->last_remainder = remainder; // 设置last_remainder为remainder remainder->bk = remainder->fd = unsorted_chunks(av); //如果是remainder在large bin的范围,则把fd_nextsize,fd_nextsize清零 if (!in_smallbin_range(remainder_size)) { remainder->fd_nextsize = NULL; remainder->fd_nextsize = NULL; } //设置victim的size set_head(victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); //设置remainder的size set_head(remainder, remainder_size | PREV_INUSE); //设置remainder的物理相邻的下一个chunk的prev_size set_foot(remainder, remainder_size); check_malloced_chunk(av, victim, nb);//默认不做任何操作 void *p = chunk2mem(victim);//将chunk指针转化为mem指针 alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做 return p; } //把victim从unsorted bin 中移除 unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av); //如果 victim 的size 与申请的size相等 if (size == nb) { //设置victim物理相邻的下一个chunk的prev_inuse位 set_inuse_bit_at_offset (victim, size); //如果av不是main_arena 也就是说如果不是主进程,设置NON_MAIN_ARENA位 if (av != &main_arena) set_non_main_arena (victim); #if USE_TCACHE //如果对应的tcache没有满,则把他加入到tcache中,设置return标志 if (tcache_nb && tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put (victim, tc_idx); return_cached = 1; continue; } else//如果对应的tcache满了,则直接返回。 { #endif check_malloced_chunk(av, victim, nb); // 默认不做任何操作 void *p = chunk2mem(victim);//把chunk转换为mem指针 alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做 return p; #if USE_TCACHE } #endif } //如果在smallbin的范围,则放到对应的small bin中 if (in_smallbin_range (size)) { victim_index = smallbin_index(size);//获取size对应的smallbin的index bck = bin_at(av, victim_index);//bck指向size对应的smallbin的链表头 //fwd指向size对应的smallbin的链表中的新加入的chunk(small bin使用头插法) fwd = bck->fd; } else//如果不在smallbin的范围,也就是说在large bin 的范围 { victim_index = largebin_index(size);//获取size对应的large bin的index bck = bin_at(av, victim_index);//bck指向size对应的large bin的链表头 fwd = bck->fd;//fwd指向size对应的large bin的链表中的新加入的chunk //如果large bin 非空,在largbin进行按顺序插入 if (fwd != bck) { size |= PREV_INUSE; assert((bck->bk->size & NON_MAIN_ARENA) == 0); /* large bin中的chunk是按从大到小排列的,如果size < large bin 的最后一个chunk,说明size是这个large bin中的最小的,我们把它 加入到此large bin尾部。 */ if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) { fwd = bck; bck = bck->bk; /* large bin 中size最小的chunk的fd_nextsize会指向size最大的 那个chunk,也就是首部的chunk。同样,large bin 中size最大的 chunk的bk_nextsize会指向size最小的那个chunk。 victim的bk_nextsize指向large bin原来最小的chunk,它的 bk_nextsize指向最大的那个chunk。那么原来的最小的就成了第二小的了。 把它fd_nextsize和bk_nextsize都修正。 */ victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; //最大size的chunk的bk_nextsize,和原来最小chunk的bk_nextsize都指向victim fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else //如果victim不是large bin 中最小的chunk { //检查NON_MAIN_ARENA位是否为0 assert (chunk_main_arena (fwd)); //从大到小(从头到尾)找到合适的位置 while ((unsigned long) size < chunksize_nomask (fwd)) { fwd = fwd->fd_nextsize; assert (chunk_main_arena (fwd)); } //如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了 if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd)) fwd = fwd->fd; else { //size不相等,即size>fwd->size,把victim加入到纵向链表中 victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; } } else //如果large bin 为空,将victim加入到纵向列表 victim->fd_nextsize = victim->bk_nextsize = victim; } //#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i)) mark_bin(av, victim_index); //把victim加入到的bin的表示为非空 //把victim加入到large bin的链表中 victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; #if USE_TCACHE ++tcache_unsorted_count; //若达到tcache_unsorted_limit限制且之前已经存入过chunk则在此时取出(默认无限制) if (return_cached && mp_.tcache_unsorted_limit > 0 && tcache_unsorted_count > mp_.tcache_unsorted_limit) { return tcache_get (tc_idx);//返回size对应的tcache的第一个chunk } #endif #define MAX_ITERS 10000 if (++iters >= MAX_ITERS) break; }
-
如果unsorted bin中也找不到合适的chunk,且在large bin的范围,继续在largebin中找
#if USE_TCACHE //如果上面没有取出,且之前已经存入过chunk则在此时取出 if (return_cached) { return tcache_get (tc_idx); } #endif //如果unsorted bin中也找不到合适的chunk,且在large bin的范围,继续在largebin中找 if (!in_smallbin_range (nb)) { bin = bin_at (av, idx); //如果对应的 bin 不为空 且 其中最大的chunk也很比我们想要的nb大 if ((victim = first (bin)) != bin && (unsigned long) chunksize_nomask (victim) >= (unsigned long) (nb)) { // 反向遍历nextsize纵向链表(从小到大),找到第一个比size大的chunk victim = victim->bk_nextsize; while (((unsigned long) (size = chunksize (victim)) < (unsigned long) (nb))) victim = victim->bk_nextsize; /* 如果取出的chunk不是bin的最后一个chunk,同时该chunk有大小相同的chunk连接在一起 它就会取它前面的那个chunk即 chunk->fd ,因为大小相同的chunk只有一个会被串在 nextsize链上这可以避免额外的bk_nextsize和fd_nextsize的赋值 */ if (victim != last (bin) && chunksize_nomask (victim) == chunksize_nomask (victim->fd)) victim = victim->fd; remainder_size = size - nb;//计算切割后的大小 unlink(av, victim, bck, fwd); //通过unlink将chunk从链表移除 if (remainder_size < MINSIZE) { //如果切割后的大小不足以作为一个chunk,那么就不分割直接将其标志位设为inuse //如果不是main_arena,同时设置NO_main_arena标志位 set_inuse_bit_at_offset(victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; } else { //如果剩余的大小可以作为一个chunk //获得剩余部分的地址,放入unsorted bin中 remainder = chunk_at_offset (victim, nb); bck = unsorted_chunks (av); fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) malloc_printerr ("malloc(): corrupted unsorted chunks"); remainder->bk = bck; remainder->fd = fwd; bck->fd = remainder; fwd->bk = remainder; //如果剩余部分的大小在largin bin的范围,则清空nextsize字段 if (!in_smallbin_range (remainder_size)) { remainder->fd_nextsize = NULL; remainder->bk_nextsize = NULL; } //设置victim的size set_head(victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); //设置remainder的size set_head(remainder, remainder_size | PREV_INUSE); //设置remainder的物理相邻的下一个chunk的prev_size set_foot(remainder, remainder_size); } check_malloced_chunk(av, victim, nb);//默认不做任何操作 void *p = chunk2mem(victim);//将chunk指针转化为mem指针 alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做 return p; } }
-
如果通过上面的方式从最合适的 small bin 或 large bin 中都没有分配到需要的 chunk,则 查看比当前 bin 的 index 大的 small bin 或 large bin 是否有空闲 chunk 可利用来分配所需的 chunk。
/* 获取下一个相邻 bin 的空闲 chunk 链表,并获取该 bin 对应 binmap 中的 bit 位的值。binmap 字段是一个 int 数组,ptmalloc 用一个 bit 来标识该 bit 对应的 bin 中是否包含空闲 chunk。Binmap 按 block 管理,每个 block 为一个 int,共 32 个 bit,可以表示 32 个 bin 中是否有空闲 chunk 存在。使用 binmap 可以加快查找 bin 是否包含空闲 chunk。这里只查询比所需 chunk 大的 bin 中是否有空闲 chunk 可用。 */ ++idx; bin = bin_at(av, idx);//获取当前bin的下一个bin block = idx2block(idx); map = av->binmap[block];//获取block bit = idx2bit(idx);//获取block中对应的bit /* Idx2bit()宏将 idx 指定的位设置为 1,其它位清零,map 表示一个 block(unsigned int) 值,如果bit 大于 map,意味着比bit对应的bin的size大的bin中无空闲chunk,如果 map 为 0,该 block 所对应的所有 bins 中都没有空闲 chunk, 于是遍历 binmap 的下一个 block,直到找到一个不为 0 的 block 或者遍历完所有的 block。 退出循环遍历后,设置 bin 指向 block 的第一个 bit 对应的 bin,并将 bit 置为 1,表示该 block 中 bit 1 对应的 bin,这个 bin 中如果有空闲 chunk,该 chunk 的大小一定满足要求。 */ for (;;) { /* 如果bit 大于 map,意味着比该bit对应的bin的size大的bin中无空闲chunk,如果 map 为 0,该 block 所对应的所有 bins 中都没有空闲 chunk 。接着在下一个block中寻找 */ if (bit > map || bit == 0) { do { //如果block超过了范围,说明比所需chunk大的bin中没有chunk,直接使用top_chunk if (++block >= BINMAPSIZE) /* out of bins */ goto use_top; //如果block为0,这表明block中的所有bit所对应的bin没有空闲chunk } while ((map = av->binmap[block]) == 0); bin = bin_at(av, (block << BINMAPSHIFT)); bit = 1; } /* 在一个block遍历对应的 bin,直到找到一个 bit 不为 0 退出遍历,则该 bit 对于的 bin 中有空闲 chunk 存在。 */ while ((bit & map) == 0) { bin = next_bin(bin); bit <<= 1; assert(bit != 0); } //获取bin尾部的chunk victim = last(bin); /* 如果 victim 与 bin 链表头指针相同,表示该 bin 中没有空闲 chunk,binmap 中的相应位 设置不准确,将 binmap 的相应 bit 位清零,获取当前 bin 下一个 bin,将 bit 移到下一个 bit 位,即乘以 2。 */ if (victim == bin) { av->binmap[block] = map &= ~bit; /* Write through */ bin = next_bin(bin); bit <<= 1; } else { /* 当前 bin 中的最后一个 chunk 满足要求,获取该 chunk 的大小,计算切分出所需 chunk 后剩余部分的大小,然后将 victim 从 bin 的链表中取出。 */ size = chunksize(victim); assert((unsigned long) (size) >= (unsigned long) (nb)); remainder_size = size - nb;//计算分割后的大小 unlink(av, victim, bck, fwd);//从bin中取出victim /* 如果剩余部分的大小小于 MINSIZE,将整个 chunk 分配给应用层,设置 victim 的状态为 inuse,如果当前分配区为非主分配区,设置 victim 的非主分配区标志位。 */ if (remainder_size < MINSIZE) { set_inuse_bit_at_offset(victim, size); if (av != &main_arena) victim->size |= NON_MAIN_ARENA; } else { //如果剩余的大小可以作为一个chunk //获得剩余部分的地址,放入unsorted bin中 remainder = chunk_at_offset(victim, nb); bck = unsorted_chunks(av); fwd = bck->fd; //这里检查unsorted bin 中的链表头部是否合法 if (__glibc_unlikely(fwd->bk != bck)) { malloc_printerr ("malloc(): corrupted unsorted chunks 2"); } remainder->bk = bck; remainder->fd = fwd; bck->fd = remainder; fwd->bk = remainder; //设置last_remainder为刚刚分割剩余的remainder if (in_smallbin_range(nb)) av->last_remainder = remainder; //如果剩余部分的大小在largin bin的范围,则清空nextsize字段 if (!in_smallbin_range(remainder_size)) { remainder->fd_nextsize = NULL; remainder->bk_nextsize = NULL; } //设置victim的size set_head(victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); //设置remainder的size set_head(remainder, remainder_size | PREV_INUSE); //设置remainder的物理相邻的下一个chunk的prev_size set_foot(remainder, remainder_size); } check_malloced_chunk(av, victim, nb);//默认不做任何操作 void *p = chunk2mem(victim);//将chunk指针转化为mem指针 alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做 return p; } }
-
如果从所有的 bins 中都没有获得所需的 chunk,可能的情况为 bins 中没有空闲 chunk, 或者所需的 chunk 大小很大,下一步将尝试从 top chunk 中分配所需 chunk。
use_top: //如果以上都无法满足我们要申请的chunk的要求,最后使用top_chunk victim = av->top; // victim 指向top chunk size = chunksize(victim);//获取top chunk的size //如果top chunk 满足我们要申请的chunk大小要求(top chunk 的size > 我们要申请的chunk + 最小chunk的size ),那么就分割它。 if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) { remainder_size = size - nb; remainder = chunk_at_offset(victim, nb); av->top = remainder; set_head(victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); set_head(remainder, remainder_size | PREV_INUSE); check_malloced_chunk(av, victim, nb); void *p = chunk2mem(victim); alloc_perturb(p, bytes); return p; } //如果top chunk 空间不够,且fastbin中有空闲chunk //则触发malloc_consolidate函数合并fastbin中能合并chunk,然后加入到unsorted bin中,如果是与top_chunk相邻的chunk则直接与top_chunk合并, //接着返回for循环开始,从unsorted bin 中继续查找 else if (have_fastchunks(av)) { malloc_consolidate(av); /* restore original bin index */ if (in_smallbin_range(nb)) idx = smallbin_index(nb); else idx = largebin_index(nb); } //如果top chunk 空间不够,且fastbin中没有空闲chunk,就通过sysmalloc从操作系统分配内存。 else { void *p = sysmalloc(nb, av); //这里也是house of orange利用的地方 if (p != NULL) alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做 return p; }
我们总结一下 malloc
响应用户内存分配要求的具体步骤:
-
将用户的请求大小转换为实际需要分配的 chunk 空间大小。
-
获取对应大小的tcache的tc_idx,如果tc_idx合法,且对应的tcache不为空,则直接返回该tcahce中的第一个chunk,分配结束,否则进入下一步。
-
判断所需分配 chunk 的大小是否满足 chunk_size <= max_fast (max_fast 默认为 64B)。如果是的话,尝试在 fast bins 中取一个所需大小的 chunk 分配给用户。如果可以找到,取出这个chunk返回,把剩下的chunk加入到tcache中,直到tcache满了或者fastbin中不存在chunk,分配结束,否则跳到下一步。
-
判断所需大小是否处在 small bins 中,如果chunk 大小处在 small bins 中,转到下一步,否则跳到第6步。
-
根据所需分配的 chunk 的大小,找到具体所在的某个 small bin,尝试从该 bin 的尾部摘取一个恰好满足大小的 chunk,若成功,取出这个chunk返回,把剩下的chunk加入到tcache中,并设置物理相邻的下一个chunk的prev_inuse位,直到tcache满了或者small bin中不存在chunk,分配结束,否则转到第7步。
-
如果fastbin中存在空闲chunk,遍历 fast bins 中的 chunk,将相邻的 chunk 进行合并, 并链接到 unsorted bin 中。然后转到下一步。
-
到了这一步,说明需要分配的是一块大的内存,或者 tcache,fastbin和small bins 中找不到合适的 chunk,于是遍历 unsorted bin 中的 chunk。
如果 unsorted bin只有一个 chunk,并且这个 chunk 在上次分配时被分割过,并且所需分配的 chunk 大小属于 small bins,并且 chunk 的大小大于等于需要分配的大小,这种情况下就直 接将该 chunk 进行切割,分配结束。
若chunk的size刚好满足大小,且对应的tcache没有满,则把他加入到tcache中,并跳出这一次的循环继续遍历unsorted bin,如果对应的tcache满了则直接返回,分配结束。
如果以上都不满足,将根据 chunk 的空间大小将其放入 small bins 或是 large bins 中。
如果达到tcache_unsorted_limit限制(默认无限制)且之前已经存入过chunk则在此时,取出size对应的tcache的第一个chunk返回,否则转入下一步。
-
如果之前已经存入过chunk且上一步没取出,则在此时取出返回我们申请的size对应的tcache的第一个chunk,分配结束。否则进入下一步。
-
到了这一步,说明需要分配的是一块大的内存,或者tcache, fastbin,small bins 和 unsorted bin 中都找不到合适的 chunk。如果需要分配的是一块大的内存,则跳到下一步,否则跳到第11步
-
从 large bins 中按照“smallest-first,best-fit”原则,找一个合适的 chunk,从 中划分一块所需大小的 chunk,并将剩下的部分放入 unsorted bin中 。若操作成功,则 分配结束,否则跳到下一步。
-
如果通过上面的方式从最合适的fastbin,small bin 或 large bin 中都没有分配到需要的 chunk,则 查看比当前 最合适bin 的 index 大的 small bin 或 large bin 是否有空闲 chunk 可利用,来分割得到所需的 chunk,并将剩下的部分放入 unsorted bin中。若操作成功,则 分配结束,否则跳到下一步。
-
如果以上都无法满足我们要申请的chunk的要求,那么就需要操作 top chunk 来 进行分配了。判断 top chunk 大小是否满足所需 chunk 的大小,如果是,则从 top chunk 中分出一块来。否则转到下一步。
-
到了这一步,说明 top chunk 也不能满足分配要求。如果fastbin中有空闲chunk则触发malloc_consolidate函数合并fastbin中能合并chunk,然后加入到unsorted bin中,如果是与top_chunk相邻的chunk则直接与top_chunk合并。若操作成功,则 跳转到 7,否则转到下一步
-
到了这一步,说明,top chunk 也不能满足分配要求,且fastbin中也没有空闲chunk。通过sysmalloc从操作系统分配内存。