目录
前两章,我们讲解了fastbins、smallbins、和unsorted bin的实现,这一章节开始前,先总结一下整体_int_malloc的一个内存分配的逻辑和流程:
- 第一步:是判断申请内存的大小,如果在fastbin范围内的,则去av->fastbinsY的空闲链表上寻找一个chunk
- 第二步:如果不是fastbin类型 或者 fastbins上没有找到合适的chunk,则检查是否属于smallbin,如果属于则去small bins上的空闲链表,去寻找一个合适的chunk
- 第三步:如果不属于smallbin类型 或者 smallbins 上没有找到合适的chunk,则去unsorted bin上去寻找一个合适的chunk,如果找到一个大小相合的则直接分配;如果找到一个比分配内存大的chunk,则经过裁剪后分配;同时也经过整理,将unsorted 上的chunk,分配放置到smallbins上或者largebins 上
- 第四步:如果unsored上分配失败(找不到或者没有合适的内存块),则判断是否属于large类型,如果属于,则到largebins上进行分配;有合适的大小,进行裁剪后进行分配;如果没有合适的,进入下一步
- 第五步:如果largebins上分配失败,或者不属于large类型,则进入bins扫描,通过扫描bins数组,分配一个比当前内存更大一号的chunk,并将剩余的内存进行裁剪,放置到合适的bin上
- 第六步:如果扫描bins也失败,则去Top chunk上去分配;Top chunk上有合适大小的内存块,则切割后直接分配;如果没有可分配的空间,调用sysmalloc扩容Top的空间
有了以上6个步骤的分配逻辑,应该对整体_int_malloc的实现有了更加清晰的认识。下面我们将一下largebins的实现、扫描bins的方法和Top chunk的实现。
一、largebins的具体实现
largebins的定义:
- 大于等于512字节(64位机器1024字节)的chunk被称为large chunk,而保存large chunks的bin被称为large bin。
- 位于small bins后面,数组编号从64开始,后64个bin为large bins,在av->bins的数组上。
- 同一个bin上的chunk,可以大小不一定相同。
- large bins都是通过等差步长的方式进行拆分。(以32位系统为例,前32个bin步长64,后16个bin步长512,后8个步长4096,后四个32768,后2个262144)(编号63到64的步长跟)。起始bin大小为512字节(64位系统1024)。
largebins的分配步骤:
- 如果需要分配的内存大小,符合large类型,则进入largebins上,去寻找一个合适的chunk进行分配
- 如果当前的bin是空的,则需要跳过;不为空,则在当前 large bin上循环遍历寻找到一个合适大小的chunk,此chunk的size需要大于分配的内存大小
- 如果剩余块的尺寸大于MINSIZE,则可以切割,剩余部分放入unsorted bin;如果小于 MINSIZE,则修改下一个chunk的标记状态
/* 如果不是smallbin,则就是large,进行largebin的分配*/
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx); //获取bin
/* skip scan if empty or largest chunk is too small
* 跳过空闲链表是空 + chunk太小的情况 */
if ((victim = first (bin)) != bin
&& (unsigned long) chunksize_nomask (victim)
>= (unsigned long) (nb))
{
/* 循环遍历large的双向链表,直到找到合适大小的chunk */
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin)
&& chunksize_nomask (victim)
== chunksize_nomask (victim->fd))
victim = victim->fd;
//当前chunk的size 比 需要分配的nb 大多少,如果大太多,需要对chunk进行切割,将多余部分放入 unsorted bin
remainder_size = size - nb;
unlink_chunk (av, victim);
/* Exhaust 如果剩余块的尺寸大于MINSIZE,则可以切割,剩余部分放入unsorted bin;如果小于 MINSIZE,则修改下一个chunk的标记状态 */
if (remainder_size < MINSIZE)
{
//remainder_size 这里没想明白,如果remainder_size,这个chunk是不是就没法使用了?
set_inuse_bit_at_offset (victim, size); //标记victim为已使用状态
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split 如果剩余块的尺寸大于MINSIZE,则可以切割下来放入unsorted bin */
else
{
remainder = chunk_at_offset (victim, nb); //切割后的chunk
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks");
/* 挂载到unsorted bin 上面 */
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
/* 剩余到块是small类型,则将fd_nextsize和bk_nextsize设置成NULL */
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0)); //设置mchunk_size 大小
set_head (remainder, remainder_size | PREV_INUSE); //设置使用状态
set_foot (remainder, remainder_size); //设置下一个chunk->mchunk_prev_size
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);//通过chunk结构,获取data数据部分的指针地址
alloc_perturb (p, bytes); //初始化内存块
return p; //返回chunk
}
}
二、通过binmap方式加速bin扫描
binmap是什么:malloc_state数据结构中,有一个binmap[BINMAPSIZE]的数组,该数组用来标记某个bin是否为空。
binmap是如何实现:
- binmap是一个无符号的int类型的数组,数组的值为:4,相当于可以存储4个无符号int类型
- 每个int有32位bit,4个int相当于128个bit,通过这128个bit的0/1状态来标记每个bin是否为空
- binmap将又将这128个bit分割成4个block,相当于四个分区,每个block有32个bit
- 通过idx2bit函数,获取得到第i位为1其它为0的掩码为bit,然后这个值跟binmap中block的int数字为map,并比较大小,如果bit>map,则说明当前block剩余的bin都为空,直接可以跳过此block,从而达到查询加速效果
//-------------------------malloc_state中定义
/* Bitmap of bins
* 表示bin数组当中某一个下标的bin是否为空,用来在分配的时候加速
* */
unsigned int binmap[BINMAPSIZE];
//-----------------------
//即使是64位系统,也使用32位的方式映射;无符号int4个字节,每个字节8bit,一共 32位
#define BINMAPSHIFT 5
#define BITSPERMAP (1U << BINMAPSHIFT) //值为32
#define BINMAPSIZE (NBINS / BITSPERMAP) //binmap一共128bits,binmap按int分成4个block,每个block有32个bit
//一共128个bin,i从0开始到127,通过(i) >> BINMAPSHIFT,可以得出在第几个block中
#define idx2block(i) ((i) >> BINMAPSHIFT) //计算出该 bin 在 binmap 对应的 bit 属于哪个 block
//1U << BINMAPSHIFT = 32
//(1U << BINMAPSHIFT) - 1) = 31
#define idx2bit(i) ((1U << ((i) & ((1U << BINMAPSHIFT) - 1)))) //取第 i 位为1,其他位都为 0 的掩码
#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i)) //设置第 i 个 bin 在 binmap 中对应的 bit 位为 1
#define unmark_bin(m, i) ((m)->binmap[idx2block (i)] &= ~(idx2bit (i))) //设置第 i 个 bin 在 binmap 中对应的 bit 位为 0
#define get_binmap(m, i) ((m)->binmap[idx2block (i)] & idx2bit (i)) //获取第 i 个 bin 在 binmap 中对应的 bit。
三、扫描bin获取合适的chunk进行分配
如果上面都没有合适的chunk,则进行bin的扫描进行chunk的内存分配。核心分配逻辑如下:
- 首先进入一个循环,bin的搜索都是从bins数组下标从小到大开始的,chunk的大小也是从小到大排列的,所以往后搜索到的chunk的size都要大于分配内存的大小
- 判断当前的block上后续的bin是否都为空,如果为空则跳过当前的block,跳转到下一个block中去寻找chunk;如果4个block都为空没有合适的chunk,则跳转到use_top,从Top chunk上去分配一块内存
- 通过循环往后找bin,如果当前的bin只有一个chunk,或者该chunk双向链表则为空,继续进入循环
- 最终从bin上找到一个合适的chunk,对chunk进行切割,切割后的剩余部分则放入unsorted bin上面;如果切割后剩余部分不足以单独成为一个chunk,则相邻chunk进行标记前一个chunk已使用状态
/* 如果上面的方式都没有找到合适的chunk,则开始扫描全部的bin;
* 通过binmap来加速查找
* binmap:ptmalloc 用一个 bit 来标识某一个 bin 中是否包含空闲 chunk
* binmap一共128bits,16字节,4个int大小,binmap按int分成4个block,每个block有32个bit。
* */
++idx; //从bins的数组上,下一个bin开始搜索
bin = bin_at (av, idx); //获取一个bin
block = idx2block (idx); //数组下标idx转成block,block一共四个,idx为0-31 则为0;idx为32-63 则为1
map = av->binmap[block]; //获取map
bit = idx2bit (idx); //idx转bit
for (;; )
{
/* Skip rest of block if there are no more set bits in this block. */
//当前的block上,对应的bin,都是空的,则跳过当前block,进入下一个block进行搜索
if (bit > map || bit == 0)
{
do
{
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top; //如果四个block 都搜索过了,并且都是空的,则跳转到use_top进行分配
}
while ((map = av->binmap[block]) == 0);
//下一个block,从下一个block的第一个bin开始
bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}
/* Advance to bin with set bit. There must be one. */
while ((bit & map) == 0)
{
bin = next_bin (bin);
bit <<= 1;
assert (bit != 0);
}
/* Inspect the bin. It is likely to be non-empty */
//获取到bin中的最后一个chunk
victim = last (bin);
/* If a false alarm (empty bin), clear the bit. */
//如果当前的bin只有一个chunk,则该chunk双向链表则为空,继续循环
if (victim == bin)
{
av->binmap[block] = map &= ~bit; /* Write through */
bin = next_bin (bin);
bit <<= 1;
}
else
{
size = chunksize (victim); //获取chunk的size
/* We know the first chunk in this bin is big enough to use. */
assert ((unsigned long) (size) >= (unsigned long) (nb));
remainder_size = size - nb; //当前的chunk 减去需要分配的nb值
/* unlink */
unlink_chunk (av, victim);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size); //设置前一个chunk使用中
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split 这里继续进行chunk的切割动作,切割完的chunk,放入unsorted上面 */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av); //获取unsorted bin
fwd = bck->fd;
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;
/* advertise as last remainder */
if (in_smallbin_range (nb))
av->last_remainder = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);//通过chunk结构,获取data数据部分的指针地址
alloc_perturb (p, bytes);//初始化内存块
return p; //返回chunk
}
}
三、从Top chunk上去分配一块内存
Top chunk定义:top chunk相当于分配区的顶部空闲内存,当bins上都不能满足内存分配要求的时候,就会来top chunk上分配。
Top chunk的分配逻辑:
-
如果top chunk的大小 大于(分配的内存 + MINISIZE),则将该Top chunk内存切割后直接分配。MINISIZE是最小的分配内存大小,在Top上分配至少留足一个MINISIZE的chunk,因为chunk的用户区域使用中的时候会复用下一个chunk的mchunk_prev_size字段的数据空间
-
top chunk会分为两个部分:User chunk(用户请求大小)和Remainder chunk(剩余大小)。其中Remainder chunk成为新的top chunk;User chunk 则初始化后直接返回给用户
-
当top chunk大小小于用户所请求的大小时,top chunk就通过sbrk(main arena)或mmap(thread arena)系统调用来扩容。主要调用的函数:sysmalloc
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; //从av->top上获取chunk
size = chunksize (victim); //size的大小值
if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");
/* 如果top chunk的大小 大于分配的内存,则将该内存切割后直接分配 */
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb); //切割剩余的top chunk块
av->top = remainder; //调整top chunk
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); //通过chunk结构,获取data数据部分的指针地址
alloc_perturb (p, bytes);//初始化内存块
return p;//返回chunk
}
/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (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);
}
/*
Otherwise, relay to handle system-dependent cases
调用sysmalloc函数,进行top的扩容,并调整av->top指向 ,返回对应的内存chunk
*/
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}