_int_malloc
__libc_malloc
会调用malloc_hook_ini
进行初始化,然后回调__libc_malloc
函数,这时候会执行_int_malloc
开始分配内存
//堆内存分配入口函数
static void *
_int_malloc (mstate av, size_t bytes)
{
......
//将内存转换以块为单位,并判断内存大小是否合法
if (!checked_request2size (bytes, &nb))
{
__set_errno (ENOMEM);
return NULL;
}
/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
mmap. */
//如果该arenas不可以使用sysmalloc申请内存 (TODO:在lib_mallocl中同样存在arena不可用的情况为何不通过sysmalloc分配)
if (__glibc_unlikely (av == NULL))
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}
首先将需要分配的内存大小转换成chunk的大小,如果av == NULL
表面当前无arena可用,会在sysmalloc
中调用mmap分配空间并将块返回给p指针
在alloc_perturb
中存在memset操作,该操作的目的是为了防止使用已释放的和未初始化的内存
static void
alloc_perturb (char *p, size_t n)
{
if (__glibc_unlikely (perturb_byte))
memset (p, perturb_byte ^ 0xff, n);
}
fastbin
继续回到_int_malloc
中gilbc
在申请内存的时候会优先申请fastbin中的内存
#define REMOVE_FB(fb, victim, pp) \
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) \
!= victim); \
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp;
victim = *fb;
首先会get_max_fast
判断f请求大小是否属于fastbin内。如果需要分配的内存大小nb落在fastbin的范围内,首先调用fastbin_index
获得chunk大小nb
对应的fastbin索引。怎么从fastbin单链表获取所需要的块的位置已经在fastbin中介绍过了。
if (victim != NULL)
{
if (SINGLE_THREAD_P)
*fb = victim->fd;
else
REMOVE_FB (fb, pp, victim);
if (__glibc_likely (victim != NULL))
{
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);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
如果fastbin 中有所需要的块,对于单线程取出第一个空闲的chunk(victim)(并将表头指向下一个chunk),多线程则使用REMOVE_FB
移除fb。随后检查当前fast内存是否损坏。
获得空闲chunk后调用chunk2mem
返回malloc_state
结构中fd所在的位置。因为一个块被使用时被移出链表。从fd开始将其初始化(memset操作,会产生缺页中断)。即使用的是这个块的fd bk结构和下一个chunk的prev_size
和 mchunk_size
未找到则进入下一步,从smallbin
中获取内存。
smallbin
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))
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);
当申请大小小于512字节时会进入smallbin申请,同样右移4位获取idx值,然后通过bin_at
获得对应大小的smallbin空闲链表指针。bins[0]对应大小是16字节。
#define bin_at(m, i) \
(mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \
- offsetof (struct malloc_chunk, fd))
这里乘2,并且减去fd相对于malloc_chunk
中的位置是因为smallbin中存储的是fd和bk指针。
#define last(b) ((b)->bk)
bin指针指向双链表的表头,表头的前一个即表尾,如果表头和表尾的地址相同则表明该大小的small bin为空,则什么也不会做。
如果不相等,首先检验双链表的完整性,随后将该small chunk的PREV_INUSE
置位bin->bk=bck
两行维护了双向链表的完整性.
分配到块后返回。
largebin
如果在smallbin
中也无法申请到空间,则到largebin
中申请
idx = largebin_index (nb);
if (atomic_load_relaxed (&av->have_fastchunks))
malloc_consolidate (av);
前面有说过large bin的间隔不对齐,通过大小和索引的关系在对应的large bin
中寻找适合的内存
#define largebin_index_64(sz) \
(((((unsigned long) (sz)) >> 6) <= 48) ? 48 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
malloc_consolidate
函数用于将larege bin
中的chunk放入unsorted bin
中,由于并没有得到用户需求的块,所有分配成功和失败都会进入大循环。
大循环
只要small bin
分配失败,就会进入大循环
for(;;)
{
//计数
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
//当前chunk指针+大小=下一个chunk
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);
//检查单链表是否正确
if (__glibc_unlikely (size <= 2 * SIZE_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");
//申请的是小块的大小,最后一个unsorted chunk,且是last_remainder(唯一chunk),则分给last_remainder
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
//切割用户需求的chunk
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
//如果剩下的是large chunk初始化
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);
alloc_perturb (p, bytes);
return p;
}
//每遍历一个usorted chunk将其移除unsorted链表
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
/* Take now instead of binning if exact fit */
//如果该大小等于需求的大小设置已用标志位
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
这部分代码的整体意思就是遍历unsortedBin
,从中查找是否有用户要求大小的chunk,iters
用来记录循环的次数,while循环中是遍历unsorted bin
在while的结束条件为遍历到了最后一个块,首先会检查unsorted chunk
单链表是否正确然后如果是最后一个块,且是last_remainder
则会尝试分割last_remainder
。如果不能分割则取出该chunk。
while循环从尾到头依次取出unsortedbin
中的所有chunk,将该chunk对应的前一个chunk保存在bck
中,并将大小保存在size
中。在此过程中如果遇到合适大小的chunk则分配给用户使用。此外对于large bin
需要排序
tatic void * _int_malloc(mstate av, size_t bytes) {
...
for (;;) {
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)){
...
}
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
//切割用户需求的chunk
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
//如果剩下的是large chunk初始化
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);
alloc_perturb (p, bytes);
return p;
}
//每遍历一个usorted chunk将其移除unsorted链表
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
//取出所有的chunks用bck保存
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
/* Take now instead of binning if exact fit */
//如果该大小等于需求,则分配给用户使用
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
//将unsert bin中属于small bin的划分到small bin
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
//属于large 的给large
else
{
victim_index = largebin_index (size);
//bck为适合大小的bin的指针
bck = bin_at (av, victim_index);
fwd = bck->fd;
//将large bin排序
/* maintain large bins in sorted order */
//不相等则说明该bin有空闲chunk ,需要找到链表中的合适位置插入
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
//将unsorted chunk是最小的则放到最前面
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
//不是最小的需要通过while找到合适的位置
else
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
} else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
.......
}
...
}
}
继续看while循环,该段代码的意思为如果从unsortedbin中取出的chunk不符合用户要求的大小,就将该chunk合并到smallbin或者largebin中。
首先如果取出的chunk(victim)属于smallbin,就通过smallbin_index
计算需要插入的位置victim_index
,然后获取smallbin中对应位置的链表头指针保存在bck
中,最后直接插入到smallbin中,由于smallbin中的chunk不使用fd_nextsize
和bk_nextsize
指针,插入操作只需要更新bk
和fd
指针,具体的插入操作在后面。
如果取出的chunk(victim)属于largebin,通过largebin_index
计算需要插入的位置victim_index
,然后获取largebin链表头指针保存在bck
中。如果fwd
等于bck
,即bck->fd=bck
,则表示largebin中对应位置上的chunk双向链表为空,直接进入后面的else部分中,代码victim->fd_nextsize = victim->bk_nextsize = victim
表示插入到largebin中的victim是唯一的chunk,因此其fd_nextsize
和bk_nextsize
指针都指向自己。
如果fwd
不等于bck
,对应的chunk双向链表存在空闲chunk,这时就要在该链表中找到合适的位置插入了。因为largebin中的chunk链表是按照chunk大小从大到小排序的,如果victim
的size
小于bck->bk->size
即最后一个chunk的大小,则表示即将插入victim
的大小在largebin的chunk双向链表中是最小的,因此要把victim
插入到该链表的最后。
如果要插入的victim
的size
不是最小的,就要通过一个while循环遍历找到合适的位置,这里是从双向链表头bck->fd
开始遍历,利用fd_nextsize
加快遍历的速度,找到第一个size>=fwd->size
的chunk。如果size=fwd->size
,就只是改变victim
以及前后相应chunk的bk
、fd
指针就行。
mark_bin(av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
再往下,mark_bin
用来标识malloc_state
中的binmap
,标识相应位置的chunk空闲。然后就更改fd
、bk
指针,插入到双向链表中,这个插入操作同时适用于smallbin和largebin,因此放在这里。最后如果在unsortedbin中处理了超过10000个chunk,就直接退出循环,这里保证不会因为unsortedbin中chunk太多,处理的时间太长了。
在退出while循环后,尝试从largebin中分配chunk
if ((victim = first (bin)) != bin
&& (unsigned long) chunksize_nomask (victim)
>= (unsigned long) (nb))
{
victim = victim->bk_nextsize;
//将该largebin中第一个满足用户需求的chunk取出来
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. */
//如果不是最后一个bin则取出上一个fd,避免victim->fd为空
if (victim != last (bin)
&& chunksize_nomask (victim)
== chunksize_nomask (victim->fd))
victim = victim->fd;
//将largechunk分为两部分
remainder_size = size - nb;
unlink_chunk (av, victim);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split */
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);
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;
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);
alloc_perturb (p, bytes);
return p;
}
}
这部分代码从largebin中取出对应chunk,首先找到bin中第一个大于用户需求的chunk,找到victim将其拆分为两部分,第一部分是要返回给用户的chunk,第二部分分为两种情况,如果其大小小于MINSIZE
,则不能构成一个最小chunk,这种情况下就将拆开前的整个victim
返回给用户;如果大于MINSIZE
,就将拆开后的第二部分remainder
插入到unsortedbin中,然后把第一部分victim
返回给用户.
static void * _int_malloc(mstate av, size_t bytes) {
...
for (;;) {
...
++idx;
bin = bin_at (av, idx);
block = idx2block(idx);
map = av->binmap[block];
bit = idx2bit(idx);
for (;;) {
if (bit > map || bit == 0) {
do {
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
} while ((map = av->binmap[block]) == 0);
bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}
while ((bit & map) == 0) {
bin = next_bin(bin);
bit <<= 1;
assert(bit != 0);
}
victim = last(bin);
if (victim == bin) {
av->binmap[block] = map &= ~bit;
bin = next_bin(bin);
bit <<= 1;
}
else {
size = chunksize(victim);
assert((unsigned long ) (size) >= (unsigned long ) (nb));
remainder_size = size - nb;
unlink(av, victim, bck, fwd);
if (remainder_size < MINSIZE) {
set_inuse_bit_at_offset(victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
else {
remainder = chunk_at_offset(victim, nb);
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely(fwd->bk != bck)) {
errstr = "malloc(): corrupted unsorted chunks 2";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = 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);
alloc_perturb(p, bytes);
return p;
}
}
...
}
}
前面在largebin中寻找特定大小的空闲chunk,如果没找到,这里就要遍历largebin中的其他更大的chunk双向链表,继续寻找。
开头的++idx
就表示,这里要从largebin中下一个更大的chunk双向链表开始遍历。ptmalloc中用一个bit表示malloc_state
的bins
数组中对应的位置上是否有空闲chunk,bit为1表示有,为0则没有。ptmalloc通过4个block(一个block 4字节)一共128个bit管理bins
数组。因此,代码中计算的block表示对应的idx
属于哪一个block,map
就表是block对应的bit组成的二进制数字。
接下来进入for循环,如果bit > map或者 bit=0则说明整个位图里面都没有大于large的bit
位置的空闲的chunk,因此就要找下一个block
。因为后面的block
只要不等于0,就肯定有空闲chunk,并且其大小大于bit
位置对应的chunk,下面就根据block
,取出block
对应的第一个双向链表的头指针。这里也可以看出,设置map
和block
也是为了加快查找的速度。如果遍历完所有block
都没有空闲chunk,这时只能从top chunk里分配chunk了,因此跳转到use_top
。
如果有空闲chunk,接下来就通过一个while循环依次比较找出到底在哪个双向链表里存在空闲chunk,最后获得空闲chunk所在的双向链表的头指针bin
和位置bit
。
接下来,如果找到的双向链表又为空,则继续前面的遍历,找到空闲chunk所在的双向链表的头指针bin
和位置bit
。如果找到的双向链表不为空,则分割large chunk。
static void * _int_malloc(mstate av, size_t bytes) {
...
for (;;) {
...
use_top:
victim = av->top;
size = chunksize(victim);
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;
}
else if (have_fastchunks(av)) {
malloc_consolidate(av);
if (in_smallbin_range(nb))
idx = smallbin_index(nb);
else
idx = largebin_index(nb);
}
else {
void *p = sysmalloc(nb, av);
if (p != NULL)
alloc_perturb(p, bytes);
return p;
}
}
}
大循环的最后一部分,首先会从top chunk中尝试分配内存;如果失败,就检查fasbin是否有空闲内存(其他线程释放的内存),合并fastbin中的chunk放入small bin或large bin,继续进大循环。如果没有fastbin则尝试通过sysmalloc从操作系统申请内存。
至此 _int_malloc也结束了.
大循环的主要功能有:
- 将
unsorted bin
里面所有的chunk都添加到small bin
和large bin
里面去,在large bin
中所有的fastbin都已经被合并,并添加到unsorted bin
中,在合并过程中有满足用户需求的chunk 则分配给用户。 - 如果用户请求的是large chunk,那么large chunk的分配工作也是在大循环里面完成的。处理完unsorted bin紧接着就是处理large bin。
- 走到第三步说明严格按照用户请求的大小来分配堆块是不可行的,因此要向更大的bin申请堆块。这一步是通过扫描arena里面的binmap来寻找的。
- 果到这里还没分到堆块,说明所有的bin都没有合适的堆块可以分配,则尝试从 top chunk分配,如果分配不了会检查有没有fastbin(其他线程释放的),如果有则合并后进入第一步,没有则通过sysmalloc分配内存。
总结
_int_malloc的思路如下:
第一步:如果进程没有关联的分配区,就通过sysmalloc
从操作系统分配内存。
第二步:从fastbin查找对应大小的chunk并返回,如果失败进入第三步。
第三步:从smallbin查找对应大小的chunk并返回,或者将fastbin中的空闲chunk合并放入unsortedbin中,如果失败进入第四步。
第四步:遍历unsortedbin,从unsortedbin中查找对应大小的chunk并返回,根据大小将unsortedbin中的空闲chunk插入smallbin或者largebin中。进入第五步。
第五步:从largebin指定位置查找对应大小的chunk并返回,如果失败进入第六步。
第六步:从largebin中大于指定位置的双向链表中查找对应大小的chunk并返回,如果失败进入第七步。
第七步:从topchunk中分配对应大小的chunk并返回,topchunk中没有足够的空间,就查找fastbin中是否有空闲chunk,如果有,就合并fastbin中的chunk并加入到unsortedbin中,然后跳回第四步。如果fastbin中没有空闲chunk,就通过sysmalloc
从操作系统分配内存。