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从操作系统分配内存。