static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
INTERNAL_SIZE_T size; /* 块的大小 */
mfastbinptr *fb; /* 关联的 fastbin(快速块链表) */
mchunkptr nextchunk; /* 下一个连续块 */
INTERNAL_SIZE_T nextsize; /* 下一个块的大小 */
int nextinuse; /* 标记下一个块是否正在使用 */
INTERNAL_SIZE_T prevsize; /* 前一个连续块的大小 */
mchunkptr bck; /* 链接用的临时变量(后向指针) */
mchunkptr fwd; /* 链接用的临时变量(前向指针) */
size = chunksize (p);
/* 对性能无影响的小型安全检查:
分配器永远不会在地址空间末尾环绕。
因此我们可以排除一些可能因意外或某些入侵者"设计"而出现的值。 */
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
malloc_printerr ("free(): invalid pointer");
/* 我们知道每个块至少为 MINSIZE 字节大小或是 MALLOC_ALIGNMENT 的倍数。 */
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
malloc_printerr ("free(): invalid size");
check_inuse_chunk(av, p);// 检测前后堆块是否释放正常
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
/* 检查它是否已经在 tcache 中。 */
tcache_entry *e = (tcache_entry *) chunk2mem (p);
/* 双重释放时此测试会成功。然而,我们不能100%信任它
(它也会以 2^<size_t> 分之一的几率匹配随机负载数据),
因此在中止之前先验证这不是不太可能的巧合。 */
if (__glibc_unlikely (e->key == tcache_key))// 防doublefree,绕过也很简单破坏即可,第二次free前理论上修改其大小也能绕过,不过你都能修改大小了
{
tcache_entry *tmp;
size_t cnt = 0;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = REVEAL_PTR (tmp->next), ++cnt)
{ // 循环遍历对应大小的tcache列表是否有其,防止出现那极小的概率事件
if (cnt >= mp_.tcache_count)
malloc_printerr ("free(): too many chunks detected in tcache");
if (__glibc_unlikely (!aligned_OK (tmp)))
malloc_printerr ("free(): unaligned chunk detected in tcache 2");
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* 如果我们到达这里,那只是个巧合。我们浪费了几个周期,
但不会中止。 */
}
}
if (tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);// 放入
return;
}
}
}
#endif
/*
如果符合条件,将块放入快速bin以便在malloc中快速找到并使用。
*/
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
#if TRIM_FASTBINS
/*
如果设置了TRIM_FASTBINS,不要将边界顶部的块放入快速bin
*/
&& (chunk_at_offset(p, size) != av->top)
#endif
) {
if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size))
<= CHUNK_HDR_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))// 判断堆块大小是否正常
{
bool fail = true;
/* 此时我们可能没有锁,并且system_mem的并发修改可能导致误报。
获取锁后重做测试。 */
if (!have_lock)
{
__libc_lock_lock (av->mutex);
fail = (chunksize_nomask (chunk_at_offset (p, size)) <= CHUNK_HDR_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem);
//再次判断堆块大小是否正常
__libc_lock_unlock (av->mutex);
}
if (fail)
malloc_printerr ("free(): invalid next size (fast)");
}
free_perturb (chunk2mem(p), size - CHUNK_HDR_SZ); // 尝试堆释放的内存进行perturb值填充,正常情况下为0,不会尝试填充
atomic_store_relaxed (&av->have_fastchunks, true);// 原操设置为1
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);
/* 原子地将P链接到它的快速bin:P->FD = *FB; *FB = P; */
mchunkptr old = *fb, old2;
if (SINGLE_THREAD_P)// 是否多线程
{
/* 检查bin顶部不是我们正要添加的记录(即双重释放)。 */
if (__builtin_expect (old == p, 0))
malloc_printerr ("double free or corruption (fasttop)");
p->fd = PROTECT_PTR (&p->fd, old);// 获取真实地址,并将其储存到p的fd里
*fb = p;
}
else
do
{
/* 检查bin顶部不是我们正要添加的记录(即双重释放)。 */
if (__builtin_expect (old == p, 0))// 经典手法有A,B两个同样大的要free,释放顺序A->B->A即可绕过
malloc_printerr ("double free or corruption (fasttop)");
old2 = old;
p->fd = PROTECT_PTR (&p->fd, old);// 获取真实地址,并将其储存到p的fd里
}
while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2))
!= old2);// 这里是将fb中储存的原先的chunk值替换为新的,替换成功则两者不相等退出循环,不成功则存在线程冲突,再次尝试
/* 检查快速bin顶部块的大小是否与我们正在添加的块的大小相同。
只有持有锁时我们才能解引用OLD,否则它可能已被重新分配。 */
if (have_lock && old != NULL
&& __builtin_expect (fastbin_index (chunksize (old)) != idx, 0))
malloc_printerr ("invalid fastbin entry (free)");
}
/*
当它们到达时,合并其他非mmap的块。
*/
else if (!chunk_is_mmapped(p)) {// 考虑非tcache和fastbin情况
/* 如果是单线程,不要锁住arena。 */
if (SINGLE_THREAD_P)
have_lock = true;
if (!have_lock)
__libc_lock_lock (av->mutex);
nextchunk = chunk_at_offset(p, size);
/* 轻量级测试:检查块是否已经是顶部块。 */
if (__glibc_unlikely (p == av->top))
malloc_printerr ("double free or corruption (top)");
/* 或者下一个块是否超出arena的边界。 */
if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
malloc_printerr ("double free or corruption (out)");
/* 或者块实际上是否未标记为使用。 */
if (__glibc_unlikely (!prev_inuse(nextchunk)))
malloc_printerr ("double free or corruption (!prev)");
nextsize = chunksize(nextchunk);
if (__builtin_expect (chunksize_nomask (nextchunk) <= CHUNK_HDR_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0))
malloc_printerr ("free(): invalid next size (normal)");
free_perturb (chunk2mem(p), size - CHUNK_HDR_SZ);
/* 向后合并 */ // 向后指低地址
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}
if (nextchunk != av->top) {
/* 获取并清除inuse位 */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
/* 向前合并 */
if (!nextinuse) {
unlink_chunk (av, nextchunk);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);
/*
将块放入unsorted bin。块在有机会在malloc中被使用一次之前,
不会放入常规bin中。
*/
bck = unsorted_chunks(av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("free(): corrupted unsorted chunks");
p->fd = fwd;
p->bk = bck;
if (!in_smallbin_range(size))
{
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
bck->fd = p;
fwd->bk = p;
set_head(p, size | PREV_INUSE);
set_foot(p, size);
check_free_chunk(av, p);// 等同do_check_free_chunk检测是否正常释放,从块头 → 块大小对齐 → 前后块使用位 → footer 字段 → 空闲链表指针 → 哨兵块这几个维度,完整地验证一个已被释放的内存块在元数据层面是否保持内部一致性。
}
/*
如果块与当前内存高端相邻,则合并到顶部
*/
else { // 直接合top合并
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
check_chunk(av, p);
}
/*
如果释放一个大空间,合并可能相邻的块。然后,如果未使用的顶端内存总量
超过trim阈值,要求malloc_trim减少顶部。
除非max_fast为0,否则我们不知道快速bin是否与顶部相邻,
因此除非快速bin被合并,否则无法确定是否达到阈值。
但我们不想在每次释放时都合并。作为折衷,
仅在达到FASTBIN_CONSOLIDATION_THRESHOLD时执行合并。
*/
if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
if (atomic_load_relaxed (&av->have_fastchunks))
// FASTBIN_CONSOLIDATION_THRESHOLD为65536,可以认为通过上面处理size大于65536且存在fastbin会触发一次malloc_consolidate
malloc_consolidate(av);
if (av == &main_arena) {
#ifndef MORECORE_CANNOT_TRIM
if ((unsigned long)(chunksize(av->top)) >=
(unsigned long)(mp_.trim_threshold))
systrim(mp_.top_pad, av);// 通过top_chunk大小大于trim_threshold(0x20000)则会将部分内存归还给系统
// 跳转主线4->分支1-收缩top_chunk
#endif
} else {
/* 即使顶部块不大,也始终尝试heap_trim(),
因为相应的堆可能会消失。 */
heap_info *heap = heap_for_ptr(top(av));
assert(heap->ar_ptr == av);
heap_trim (heap, mp_.top_pad);// 跳转主线4->分支2-线程heap收缩
}
}
if (!have_lock)
__libc_lock_unlock (av->mutex);
}
/*
如果块是通过mmap分配的,通过munmap()释放。
*/
else {
munmap_chunk (p);
}
}