malloc分析
大致流程
__libc_malloc
我们平时所用的malloc函数在glibc中实际调用的是__libc_malloc函数,通过string_alias宏强命名为malloc使用的。__libc_malloc函数主要操作为
- 判断__malloc_hook是否有指定hook,有则直接调用__malloc_hook
- 如果没有设置__malloc_hook,则获取当前线程的堆分配器,并加锁
- 调用_int_malloc函数获取chunk
- 对分配的chunk进行校验处理并返回
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook); // 获取__malloc_hook
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0)); // 执行__malloc_hook
arena_get (ar_ptr, bytes); // 获取当前线程的堆分配器并加锁
victim = _int_malloc (ar_ptr, bytes); // 进行分配
if (!victim && ar_ptr != NULL) // 如果获取chunk失败,并且分配器不为空
{
LIBC_PROBE (memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry (ar_ptr, bytes); // 尝试重新获取堆分配器
victim = _int_malloc (ar_ptr, bytes); // 再次分配
}
if (ar_ptr != NULL) // 释放分配器锁
(void) mutex_unlock (&ar_ptr->mutex);
// 断言获取的chunk是否为空、chunk是否为mmap分配、分配器是否匹配
assert (!victim || chunk_is_mmapped (mem2chu(victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
return victim;
}
_int_malloc
实际上所有的分配逻辑都是在_int_malloc中进行处理的,它分为几个处理过程
- 如果堆分配器为空,则直接通过sysmalloc分配
- 从fastbin中获取chunk
- 从smallbin中获取chunk
- 从unsortedbin中获取chunk
- 从largebin中获取chunk
- 从top chunk中分割chunk
sysmalloc
checked_request2size (bytes, nb); // 判断申请大小是否合法并转换为chunk大小
if (__glibc_unlikely (av == NULL)) // 判断堆分配器是否为空
{
void *p = sysmalloc (nb, av); // 直接通过sysmalloc分配
if (p != NULL) // 分配到chunk
alloc_perturb (p, bytes); // 如果设置perturb_type,则初始化chunk内存为perturb_type ^ 0xff
return p;
}
bytes是要申请的内存大小,首先checked_request2size会检查byts是否太大,导致在填充和对齐时将chunk大小绕回零。然后checked_request2size将bytes对齐并转换为chunk大小,也就是将nb对齐内存,然后加上堆块头。
fastbin
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) // 判断nb大小是否小于fastbin最大的大小
{
idx = fastbin_index (nb); // 获取nb对应fastbin的index
mfastbinptr *fb = &fastbin (av, idx); // 获取该bin的指针
mchunkptr pp = *fb; // 获取该bin中第一个chunk
do
{
victim = pp;
if (victim == NULL) // fastbin为空或者到链表结尾直接退出
break;
}
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim); // 将bin中第一个chunk解链
if (victim != 0) // 如果bin中不为空的话
{
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)) // 如果这个chunk的大小不符合bin位置,则报错
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
check_remalloced_chunk (av, victim, nb); // DEBUG模式下进行详细检测
void *p = chunk2mem (victim); // chunk转mem
alloc_perturb (p, bytes); // 如果设置了perturb_type,则将mem初始化为perturb_type^0xff
return p;
}
}
get_max_fast宏是获取global_max_fast全局变量的值,代表fastbin最大的大小,只要小于这个值的chunk size都属于fastbin范围。
smallbin
if (in_smallbin_range (nb)) // 申请大小在small bin范围
{
idx = smallbin_index (nb); // 获取nb对应smallbin中的index
bin = bin_at (av, idx); // 获取该bin的指针
if ((victim = last (bin)) != bin) // 获取bin中最后一个chunk(先进先出)
{
if (victim == 0) // smallbin没有初始化(获取到的chunk指针为NULL)
malloc_consolidate (av); // 初始化堆管理器, 合并fastbin chunk到unsortedbin
else
{
bck = victim->bk; // 获取chunk前一个chunk
if (__glibc_unlikely (bck->fd != victim)) // 如果前一个chunk的fd不指向当前chunk则报错
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb); // 设置物理临近的下一个chunk的inuse位
bin->bk = bck; // 将chunk解链
bck->fd = bin;
if (av != &main_arena) // 判断是否为主分配器,否则设置标志
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb); // DEBUG模式下详细检测
void *p = chunk2mem (victim); // chunk转mem
alloc_perturb (p, bytes); // 如果设置了pertub_type则初始化内存为pertub_type^0xff
return p;
}
}
}
malloc_consolidate这个操作在很多地方都会被调用到,主要是将fastbin中的chunk合并处理,放入到unsortedbin中或与top chunk合并
unsortedbin
一般进行到这步说明申请的chunk比较大,在largebin范围,首先也会进行合并fastbin的操作
else
{
idx = largebin_index (nb); // 获取对应largebin的index
if (have_fastchunks (av)) // 如果fastbin中有chunk
malloc_consolidate (av); // 合并fastbin chunk到unsortedbin
}
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 获取unsortedbin中最后一个chunk(先进先出)
{
bck = victim->bk; // 该chunk的前一个chunk
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0) // 判断这个chunk的大小是否小于2*SIZE_T,或者大小大于系统分配堆内存
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim); // 获取chunk大小
if (in_smallbin_range (nb) && // 如果申请大小在smallbin范围
bck == unsorted_chunks (av) && // 同时这个chunk是unsortedbin唯一的chunk
victim == av->last_remainder && // 并标记为remainder chunk
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // chunk大小大于申请大小
{
remainder_size = size - nb; // 计算出remainder chunk分配后剩余大小
remainder = chunk_at_offset (victim, nb); // 计算出新的remainder chunk指针
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; // 将该chunk加入到unsortedbin
av->last_remainder = remainder; // 设置分配器last_remainder
remainder->bk = remainder->fd = unsorted_chunks (av); // 设置该chunk的fd和bk指针加入链表
if (!in_smallbin_range (remainder_size)) // 如果remainder size不属于smallbin范围,也就是largebin范围
{
remainder->fd_nextsize = NULL; // 设置fd_nextsize和bk_nextsize为NULL,以供后续largebin使用
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE | // 设置分配出去的chunk标志
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE); // 设置剩余部分chunk的标志
set_foot (remainder, remainder_size); // 设置剩余部分chunk的下一个chunk的prev_size为remainder size
check_malloced_chunk (av, victim, nb); // DEBUG模式下详细检测
void *p = chunk2mem (victim); // chunk转mem
alloc_perturb (p, bytes); // 如果设置了pertub_type则初始化内存为pertub_type^0xff
return p;
}
unsorted_chunks (av)->bk = bck; // 将当前chunk解链
bck->fd = unsorted_chunks (av);
if (size == nb) // 如果申请大小和chunk size刚好相等
{
set_inuse_bit_at_offset (victim, size); // 设置物理相邻的下一个chunk inuse标志
if (av != &main_arena) // 设置main_arena标志
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb); // DEBUG模式下详细检测
void *p = chunk2mem (victim); // chunk转mem
alloc_perturb (p, bytes); // 如果设置了pertub_type则初始化内存为pertub_type^0xff
return p;
}
if (in_smallbin_range (size)) // 如果chunk大小在smallbin范围
{
victim_index = smallbin_index (size); // 获取chunk对应的smallbin index
bck = bin_at (av, victim_index); // 获取smallbin指针
fwd = bck->fd; // 获取bin中第一个chunk
}
else
{
victim_index = largebin_index (size); // 获取chunk对应的largebin index
bck = bin_at (av, victim_index); // 获取largebin指针
fwd = bck->fd; // 获取bin中第一个chunk
if (fwd != bck) // 如果largebin不为空
{
size |= PREV_INUSE;
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) // 如果chunk比largebin中最小的还小
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd; // 设置chunk的fd_nextsize为最大chunk
victim->bk_nextsize = fwd->fd->bk_nextsize; // 将chunk的bk_nextsize为原本最小的chunk
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; // 将chunk加入的size链表
}
else
{
assert ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size) // 遍历到刚好chunk大于或等于的链表处
{
fwd = fwd->fd_nextsize; // 下一个更小的chunk
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}
if ((unsigned long) size == (unsigned long) fwd->size) // 如果刚好等于这个chunk
fwd = fwd->fd; // 将要把chunk插入到这个chunk后面
else
{
victim->fd_nextsize = fwd; // 将chunk加入size链表中该chunk前面
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim; // 直接将chunk加入到size链表中
}
mark_bin (av, victim_index); // 设置bitmap表
victim->bk = bck; //NOTE - 将chunk链入bin中
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
#define MAX_ITERS 10000
if (++iters >= MAX_ITERS) // 最多循环次数
break;
}
if (!in_smallbin_range (nb)) // 如果申请大小大于smallbin
{
bin = bin_at (av, idx); // 获取largbin对应的指针
if ((victim = first (bin)) != bin && // 如果bin不为空,或者bin中最大chunk大于申请大小
(unsigned long) (victim->size) >= (unsigned long) (nb))
{
victim = victim->bk_nextsize; // 获取bin中最小chunk
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb))) // 遍历到大于或者等于申请大小的chunk
victim = victim->bk_nextsize;
if (victim != last (bin) && victim->size == victim->fd->size) // 如果这个chunk不是最后一个chunk,并且chunk的大小等于下一个chunk的大小(也就代表有更新的chunk释放和当前chunk大小相同)
victim = victim->fd; // 用最新释放的chunk
remainder_size = size - nb; // 计算出分割后剩余大小
unlink (av, victim, bck, fwd); // 将chunk解链
if (remainder_size < MINSIZE) // 如果剩余部分大小小于chunk最低大小
{
set_inuse_bit_at_offset (victim, size); // 那么整个chunk都分配出去,设置chunk的标志位
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
else
{
remainder = chunk_at_offset (victim, nb); // 计算出剩余部分chunk的指针
bck = unsorted_chunks (av); // 获取unsortedbin指针
fwd = bck->fd; // 获取unsortedbin中第一个chunk
if (__glibc_unlikely (fwd->bk != bck)) // 如果unsortedbin第一个chunk的bk不指向unsortedbin则报错
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck; // 将remainder链入unsortedbin
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size)) // 如果剩余部分属于largebin,那么设置nextsize指针为NULL
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE | // 设置分配出去的chunk标志
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE); // 设置剩余部分chunk的标志
set_foot (remainder, remainder_size); // 设置剩余部分chunk的下一个chunk的prev_size为remainder size
}
check_malloced_chunk (av, victim, nb); // DEBUG模式下详细检测
void *p = chunk2mem (victim); // chunk转mem
alloc_perturb (p, bytes); // 如果设置了pertub_type则初始化内存为pertub_type^0xff
return p;
}
}
本文作者:c3n1g
本文链接:https://www.cnblogs.com/musing/p/18424041
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步