5.8.2 _int_free()
_int_free()
函数的实现源代码如下:
static void
#ifdef ATOMIC_FASTBINS
_int_free(mstate av, mchunkptr p, int have_lock)
#else
_int_free(mstate av, mchunkptr p)
#endif
{
INTERNAL_SIZE_T size; /* its size */
mfastbinptr* fb; /* associated fastbin */
mchunkptr nextchunk; /* next contiguous chunk */
INTERNAL_SIZE_T nextsize; /* its size */
int nextinuse; /* true if nextchunk is used */
INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */
mchunkptr bck; /* misc temp for linking */
mchunkptr fwd; /* misc temp for linking */
const char *errstr = NULL;
#ifdef ATOMIC_FASTBINS
int locked = 0;
#endif
size = chunksize(p);
获取需要释放的chunk的大小。
/* Little security check which won't hurt performance: the
allocator never wrapps around at the end of the address space.
Therefore we can exclude some size values which might appear
here by accident or by "design" from some intruder. */
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
{
errstr = "free(): invalid pointer";
errout:
#ifdef ATOMIC_FASTBINS
if (! have_lock && locked)
(void)mutex_unlock(&av->mutex);
#endif
malloc_printerr (check_action, errstr, chunk2mem(p));
return;
}
/* We know that each chunk is at least MINSIZE bytes in size. */
if (__builtin_expect (size < MINSIZE, 0))
{
errstr = "free(): invalid size";
goto errout;
}
check_inuse_chunk(av, p);
上面的代码用于安全检查,
chunk
的指针地址不能溢出,
chunk
的大小必须大于等于
MINSIZE
。
/*
If eligible, place chunk on a fastbin so it can be found
and used quickly in malloc.
*/
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
#if TRIM_FASTBINS
/*
If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins
*/
&& (chunk_at_offset(p, size) != av->top)
#endif
) {
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
{
#ifdef ATOMIC_FASTBINS
/* We might not have a lock at this point and concurrent modifications
of system_mem might have let to a false positive. Redo the test
after getting the lock. */
if (have_lock
|| ({ assert (locked == 0);
mutex_lock(&av->mutex);
locked = 1;
chunk_at_offset (p, size)->size <= 2 * SIZE_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem;
}))
#endif
{
errstr = "free(): invalid next size (fast)";
goto errout;
}
#ifdef ATOMIC_FASTBINS
if (! have_lock)
{
(void)mutex_unlock(&av->mutex);
locked = 0;
}
#endif
}
如果当前
free
的
chunk
属于
fast bins
,查看下一个相邻的
chunk
的大小是否小于等于
2*SIZE_SZ
,下一个相邻
chunk
的大小是否大于分配区所分配的内存总量,如果是,报错。这里计算下一个相邻
chunk
的大小似乎有点问题,因为
chunk
的
size
字段中包含了一些标志位,正常情况下下一个相邻
chunk
的
size
中的
PREV_INUSE
标志位会置位,但这里就是要检出错的情况,也就是下一个相邻
chunk
的
size
中标志位都没有置位,并且该
chunk
大小为
2*SIZE_SZ
的错误情况。如果开启了
ATOMIC_FASTBINS
优化,并且调用本函数前没有对分配区加锁,
所以读取分配区所分配的内存总量需要对分配区加锁,检查完以后,释放分配区的锁。
if (__builtin_expect (perturb_byte, 0))
free_perturb (chunk2mem(p), size - SIZE_SZ);
set_fastchunks(av);
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);
设置当前分配区的fast bin flag,表示当前分配区的fast bins中已有空闲chunk。然后根据当前free的chunk大小获取所属的fast bin。
#ifdef ATOMIC_FASTBINS
mchunkptr fd;
mchunkptr old = *fb;
unsigned int old_idx = ~0u;
do
{
/* Another simple check: make sure the top of the bin is not the
record we are going to add (i.e., double free). */
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
if (old != NULL)
old_idx = fastbin_index(chunksize(old));
p->fd = fd = old;
}
while ((old = catomic_compare_and_exchange_val_rel (fb, p, fd)) != fd);
if (fd != NULL && __builtin_expect (old_idx != idx, 0))
{
errstr = "invalid fastbin entry (free)";
goto errout;
}
如果开启了
ATOMIC_FASTBINS
优化,使用
lock-free
技术实现
fast bin
的单向链表插入操作。对
lock-free
的描述,请参见
5.7.2.1
节。这里也没有
ABA
问题,比如当前线程获取
*fb
并保存到
old
中,在调用
cas
原子操作前,
B
线程将
*fb
修改为
x
,如果
B
线程加入了新的
chunk
,则
x->fb
指向
old
,如果
B
线程删除了
old
,则
x
为
old->fb
。如果
C
线程将
*fb
修改为
old
,则可能将
B
线程加入的
chunk x
删除,或者
C
将
B
删除的
old
又重新加入。这两种情况,都不会导致链表出错,所以不会有
ABA
问题。
#else
/* Another simple check: make sure the top of the bin is not the
record we are going to add (i.e., double free). */
if (__builtin_expect (*fb == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
if (*fb != NULL
&& __builtin_expect (fastbin_index(chunksize(*fb)) != idx, 0))
{
errstr = "invalid fastbin entry (free)";
goto errout;
}
p->fd = *fb;
*fb = p;
如果没有开启了ATOMIC_FASTBINS优化,将free的chunk加入fast bin的单向链表中,修改过链表表头为当前free的chunk。同时需要校验是否为double free错误,校验表头不为NULL情况下,保证表头chunk的所属的fast bin与当前free的chunk所属的fast bin相同。
#endif
}
/*
Consolidate other non-mmapped chunks as they arrive.
*/
else if (!chunk_is_mmapped(p)) {
#ifdef ATOMIC_FASTBINS
if (! have_lock) {
# if THREAD_STATS
if(!mutex_trylock(&av->mutex))
++(av->stat_lock_direct);
else {
(void)mutex_lock(&av->mutex);
++(av->stat_lock_wait);
}
# else
(void)mutex_lock(&av->mutex);
# endif
locked = 1;
}
#endif
如果当前free的chunk不是通过mmap()分配的,并且当前还没有获得分配区的锁,获取分配区的锁。
nextchunk = chunk_at_offset(p, size);
获取当前free的chunk的下一个相邻的chunk。
/* Lightweight tests: check whether the block is already the
top block. */
if (__builtin_expect (p == av->top, 0))
{
errstr = "double free or corruption (top)";
goto errout;
}
/* Or whether the next chunk is beyond the boundaries of the arena. */
if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
{
errstr = "double free or corruption (out)";
goto errout;
}
/* Or whether the block is actually not marked used. */
if (__builtin_expect (!prev_inuse(nextchunk), 0))
{
errstr = "double free or corruption (!prev)";
goto errout;
}
安全检查,当前
free
的
chunk
不能为
top chunk
,因为
top chunk
为空闲
chunk
,如果再次
free
就可能为
double
free
错误了。如果当前
free
的
chunk
是通过
sbrk()
分配的,并且下一个相邻的
chunk
的地址已经超过了
top chunk
的结束地址,超过了当前分配区的结束地址,报错。如果当前
free
的
chunk
的下一个相邻
chunk
的
size
中标志位没有标识当前
free chunk
为
inuse
状态,可能为
double free
错误。
nextsize = chunksize(nextchunk);
if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0))
{
errstr = "free(): invalid next size (normal)";
goto errout;
}
if (__builtin_expect (perturb_byte, 0))
free_perturb (chunk2mem(p), size - SIZE_SZ);
计算当前free的chunk的下一个相邻chunk的大小,该大小如果小于等于2*SIZE_SZ或是大于了分配区所分配区的内存总量,报错。
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(p, bck, fwd);
}
如果当前
free
的
chunk
的前一个相邻
chunk
为空闲状态,与前一个空闲
chunk
合并。计算合并后的
chunk
大小,并将前一个相邻空闲
chunk
从空闲
chunk
链表中删除。
if (nextchunk != av->top) {
/* get and clear inuse bit */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
如果与当前free的chunk相邻的下一个chunk不是分配区的top chunk,查看与当前chunk相邻的下一个chunk是否处于inuse状态。
/* consolidate forward */
if (!nextinuse) {
unlink(nextchunk, bck, fwd);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);
如果与当前free的chunk相邻的下一个chunk处于inuse状态,清除当前chunk的inuse状态,则当前chunk空闲了。否则,将相邻的下一个空闲chunk从空闲链表中删除,并计算当前chunk与下一个chunk合并后的chunk大小。
/*
Place the chunk in unsorted chunk list. Chunks are
not placed into regular bins until after they have
been given one chance to be used in malloc.
*/
bck = unsorted_chunks(av);
fwd = bck->fd;
if (__builtin_expect (fwd->bk != bck, 0))
{
errstr = "free(): corrupted unsorted chunks";
goto errout;
}
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;
将合并后的chunk加入unsorted bin的双向循环链表中。如果合并后的chunk属于large bins,将chunk的fd_nextsize和bk_nextsize设置为NULL,因为在unsorted bin中这两个字段无用。
set_head(p, size | PREV_INUSE);
set_foot(p, size);
设置合并后的空闲chunk大小,并标识前一个chunk处于inuse状态,因为必须保证不能有两个相邻的chunk都处于空闲状态。然后将合并后的chunk加入unsorted bin的双向循环链表中。最后设置合并后的空闲chunk的foot,chunk空闲时必须设置foot,该foot处于下一个chunk的prev_size中,只有chunk空闲是foot才是有效的。
check_free_chunk(av, p);
}
/*
If the chunk borders the current high end of memory,
consolidate into top
*/
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
check_chunk(av, p);
}
如果当前
free
的
chunk
下一个相邻的
chunk
为
top chunk
,则将当前
chunk
合并入
top chunk
,修改
top chunk
的大小。
/*
If freeing a large space, consolidate possibly-surrounding
chunks. Then, if the total unused topmost memory exceeds trim
threshold, ask malloc_trim to reduce top.
Unless max_fast is 0, we don't know if there are fastbins
bordering top, so we cannot tell for sure whether threshold
has been reached unless fastbins are consolidated. But we
don't want to consolidate on each free. As a compromise,
consolidation is performed if FASTBIN_CONSOLIDATION_THRESHOLD
is reached.
*/
if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
if (have_fastchunks(av))
malloc_consolidate(av);
如果合并后的chunk大小大于64KB,并且fast bins中存在空闲chunk,调用malloc_consolidate()函数合并fast bins中的空闲chunk到unsorted bin中。
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的大小大于heap的收缩阈值,调用sYSTRIm()函数首先heap。
#endif
} else {
/* Always try heap_trim(), even if the top chunk is not
large, because the corresponding heap might go away. */
heap_info *heap = heap_for_ptr(top(av));
assert(heap->ar_ptr == av);
heap_trim(heap, mp_.top_pad);
如果为非主分配区,调用heap_trim()函数收缩非主分配区的sub_heap。
}
}
#ifdef ATOMIC_FASTBINS
if (! have_lock) {
assert (locked);
(void)mutex_unlock(&av->mutex);
}
#endif
如果开启了ATOMIC_FASTBINS优化并获得分配区的锁,则对分配区解锁。
}
/*
If the chunk was allocated via mmap, release via munmap(). Note
that if HAVE_MMAP is false but chunk_is_mmapped is true, then
user must have overwritten memory. There's nothing we can do to
catch this error unless MALLOC_DEBUG is set, in which case
check_inuse_chunk (above) will have triggered error.
*/
else {
#if HAVE_MMAP
munmap_chunk (p);
如果当前free的chunk是通过mmap()分配的,调用munma_chunk()释放内存。
#endif
}
}
分享到:
相关推荐
《Glibc内存管理--ptmalloc2源代码分析》 Glibc是GNU项目提供的C语言标准库,它在Linux系统中扮演着至关重要的角色。其中,内存管理是Glibc中的核心部分,它负责程序运行时的内存分配与释放,对系统的性能有着深远...
在分析glibc内存管理的ptmalloc源代码之前,我们需要先了解一些基础知识,包括操作系统对内存的分配和管理方法,以及glibc内存分配机制。 内存管理是操作系统的一个核心功能,它负责维护和管理计算机系统中的物理和...
《glibc内存管理ptmalloc源代码分析》是一份深入探讨Linux系统中glibc库内存管理机制的专业资料。glibc,全称GNU C Library,是Linux操作系统下广泛使用的C语言标准库,其中ptmalloc是glibc中负责动态内存分配的核心...
glibc内存管理ptmalloc源代码分析
在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念。内存管理是操作系统和编程语言库中的核心组件,它负责有效地分配和回收内存,以确保程序的高效运行和资源的有效利用。 2.1 X86...
标题与描述中的关键词“Glibc内存管理Ptmalloc2源代码分析”指向了对Glibc中Ptmalloc2这一内存管理器的深入探讨。本文将围绕这一主题,详细解析Ptmalloc2的核心概念、设计原则、数据结构以及关键功能,同时结合给定...
标题与描述概述的知识点主要集中在glibc内存管理的细节,特别是ptmalloc的源代码分析,这对于深入理解C库如何高效地处理内存分配和回收至关重要。以下是对这些知识点的详细解析: ### Glibc内存管理与ptmalloc ###...
在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念和Glibc中的Ptmalloc2。内存管理是操作系统和应用程序中的核心部分,它负责为程序分配和释放内存,以确保资源的有效利用和避免...
### glibc内存管理ptmalloc源代码分析 #### 1. 问题 在开发一款NoSQL系统的过程中,我们遇到一个令人头疼的问题:系统中使用的内存管理模块,在高并发、高负载的场景下,虽然已将内存释放给C运行时库(即glibc),...
### glibc内存管理ptmalloc源代码分析-清晰版 #### 一、背景介绍与文档概览 本文档针对glibc中的ptmalloc2内存管理模块进行了深入的源代码分析,旨在帮助开发者更好地理解ptmalloc的工作原理及其内部机制。文档...
`glibc`是GNU项目提供的C标准库,而`ptmalloc`是它的一部分,负责程序的内存分配与管理。`ptmalloc`优化了内存分配效率,但在某些情况下也可能成为安全漏洞的来源。 `GDB`(GNU调试器)是一款强大的调试工具,它...
本文将深入探讨内存管理的基本原理及其调试方法,特别关注于Linux环境下的Glibc内存管理库以及Ptmalloc2的具体实现。 #### 二、基础知识 ##### 2.1 X86平台Linux进程内存布局 **2.1.1 32位模式下进程内存经典布局...
通过对glibc库中的ptmalloc源代码进行深入分析,我们不仅了解了其内部实现机制,还能够针对实际应用中的内存管理问题提出有效的解决方案。这对于提升程序性能、减少内存碎片以及提高系统的整体稳定性具有重要意义。...
学习自《glibc内存管理ptmalloc源代码分析》庄明强 著 部分资料参考自互联网 chunk 描述: 当用户通过malloc等函数申请空间时,实际上是从堆中分配内存 目前 Linux 标准发行版中使用的是 glibc 中的堆分配器:...
《堆漏洞的利用技巧》 堆溢出是计算机安全领域中的一个重要...同时,阅读glibc内存管理的源代码分析对于提高理解和实践能力非常有帮助。通过这些知识的学习,不仅可以增强安全意识,也能提升在CTF竞赛中的攻防能力。