1. glibc公共接口
#define SIZE_SZ sizeof(size_t) //32位机下位4个字节,64位机下位8个字节
#define MALLOC_ALIGNMENT 16 //不管是32位还是64位,均为16
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize)) //在32位机下大小为16字节,64位机位32字节
/*! 32位为16,64位为32 */
#define MINSIZE \
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))
/*! request2size要求16字节对齐,即大小能被16整除;在32位机下最小值为16,64位机为32 */
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
#define NBINS 128
------------------------------------------------------------------------------------------------------------------------------
2. glibc内存管理最小单元,即每个被分配给用户的内存,在glibc内部都具有一个对应的malloc_chunk块由glibc进行统一的管理。
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
------------------------------------------------------------------------------------------------------------------------------
2.1. nchunk_size
该字段由两部分组成,后3bit位分别代表A、M和P,A表示是否是主arena,M表示是否是mmap所分配,P代表前一个chunk是否在使用。剩余的其余bit表示当前chunk的大小。
对主arean所分配的内存,都有sbrk系统调用来完成;除主arean外的所有arean为线程arean,线程内存分配均有mmap来完成。
------------------------------------------------------------------------------------------------------------------------------
2.2. mchunk_prev_size
该字段表示前一个chunk的大小,只有当当前P bit位为0时,mchunk_prev_size才有效,当前chunk可通过偏移mchunk_prev_size长度可找到前一个chunk。找到前一个chunk块后,可将当前chunk与上一个chunk进行合并。
------------------------------------------------------------------------------------------------------------------------------
3. struct malloc_state
该结构是用来管理malloc_chunk的主体结构,所有被分配给用户的内存块,在free后都由malloc_state来进行管理。
struct malloc_state {
/* Serialize access. */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast). */
int flags;
/* Set if the fastbin chunks contain recently inserted free blocks. */
/* Note this is a bool but not all targets support atomics on booleans. */
int have_fastchunks;
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* Linked list */
struct malloc_state *next;
/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
}
------------------------------------------------------------------------------------------------------------------------------
3.1. fastbinsY[NFASTBINS]
fastbinsY是一个数组,数组大小计算如下:
#define fastbin_index(sz) ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
/* The maximum fastbin request size we support */
#define MAX_FAST_SIZE (80 * SIZE_SZ / 4) //在32位机下值为80;64位为160
------------------------------------------------------------------------------------------------------------------------------
3.1.1. fastbinsY数组大小
/*!
* 32位机的情况:
* request2size (MAX_FAST_SIZE) = 96
* fastbin_index (request2size (MAX_FAST_SIZE)) //将96右移3位得12,减2得10
* fastbin_index (request2size (MAX_FAST_SIZE)) + 1 //结果为10 + 1 = 11
* 综上可知,NFASTBINS = 11,即fastbinsY是一个大小为11的数组
* 64位机的情况:
* request2size (MAX_FAST_SIZE) = 176
* fastbin_index (request2size (MAX_FAST_SIZE)) //将176右移4位得11,减2得9
* fastbin_index (request2size (MAX_FAST_SIZE)) + 1 //结果为9 + 1 = 10
* 综上可知,NFASTBINS = 10,即fastbinsY是一个大小为10的数组
*/
#define NFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) + 1)
------------------------------------------------------------------------------------------------------------------------------
3.1.2. fastbinsY chunk范围
/*! 32位为64字节,64位为128字节 */
#define DEFAULT_MXFAST (64 * SIZE_SZ / 4)
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
/*! 32位为64字节,64位为128字节 */
#define set_max_fast(s) \
global_max_fast = (((s) == 0) \
? SMALLBIN_WIDTH : ((s + SIZE_SZ) & ~MALLOC_ALIGN_MASK))
3.1.3. fastbinsY最多能用多少个??
根据fastbin_index可知:32位和64位系统最对能用7个
它们之间的大小情况如下:
32位 64位
fastbinsY[0] = 16 fastbinsY[0] = 32
fastbinsY[2] = 32 fastbinsY[1] = 48
fastbinsY[4] = 48 fastbinsY[2] = 64
fastbinsY[6] = 64 fastbinsY[3] = 80
fastbinsY[4] = 96
fastbinsY[5] = 112
fastbinsY[6] = 128
------------------------------------------------------------------------------------------------------------------------------
4. bins[NBINS * 2 - 2]
bins被分为unsorted、small和large bins,bins[0]是不存在的
- unsorted_bin[1]:该bin中存放的chunk块是无序的,在free过程中,首先将chunk块统一放到unsorted bin中,待在后续的malloc中将其放入相应的small和large bins中或直接作为用户请求返回
- smallbin[2-63]:同一个bin中存放的大小都是相同的,两个相邻的bin之间相差2个机器字长,即32位位8字节,64位为16字节
- largebin[64-128]
------------------------------------------------------------------------------------------------------------------------------
5. 内存管理malloc和free
5.1. 内存分配顺序
fastbinsY——>smallbin——>largebin——>unsortedbin——>top
在首次进行malloc时,由于fastbinsY、smallbin、largebin和unsortedbin均为空,只能从top处获得内存。
------------------------------------------------------------------------------------------------------------------------------
5.2. top内存管理
top作为一个堆的堆顶元素,在首次请求内存时,glibc会向操作提供申请虚拟内存,以达到堆向上增长的要求,glibc默认一次性申请DEFAULT_TOP_PAD(131072,为128KB,且以内存页4KB进行对齐,这样在后续的堆增长中,都以内存页4KB进行对齐增长)大小的堆。
当用户从top请求一定大小的内存后,剩余的部分被称为last_remainder,last_remainder将会是新的top。
------------------------------------------------------------------------------------------------------------------------------
5.3. unsortedbin
在真正的free中,chunk并不会直接放到smallbin中,而是直接unsortedbin中;如果在free过程中有连续相邻的chunk块被释放,就会在free过程中进行合并,然后重新放入unsortedbin中。unsortedbin在内存分配malloc过程中,作为最后一个被选择的bin。在利用unsortedbin分配内存时,首先会对满足smallbin的请求考虑从last_remainder中进行分配内存,然后会判定从unsortedbin中拿出来的是否满足用户需求,满足则直接返回给用否,否则会一次遍历unsortedbin的chunk块,并将其从unsortedbin中移除,依次根据条件放入smallbin和largebin中。
------------------------------------------------------------------------------------------------------------------------------
5.3.1. 对满足smallbin的请求从last_remainder中分配内存
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{}
------------------------------------------------------------------------------------------------------------------------------
5.3.2. 判断unsortedbin中的chunk块是否满足用户需求
if (size == nb) {
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
#if USE_TCACHE
/* Fill cache first, return to user only if cache fills.
We may return one of these chunks later. */
if (tcache_nb
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
else
{
#endif
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
#if USE_TCACHE
}
#endif
}
------------------------------------------------------------------------------------------------------------------------------
5.3.3. 放入smallbin中
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
------------------------------------------------------------------------------------------------------------------------------
5.3.4. 放入largebin中
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
------------------------------------------------------------------------------------------------------------------------------
5.4. 从smallbin和largebin中分配内存
待smallbin和largebin中成功放入chunk块后,在后续的内存情况中会考虑从smallbin和largebin中进行分配。
------------------------------------------------------------------------------------------------------------------------------
5.4.1. 从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);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
------------------------------------------------------------------------------------------------------------------------------
5.4.2. 从smallbin中分配内存
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);
/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin
&& (unsigned long) chunksize_nomask (victim)
>= (unsigned long) (nb))
{
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;
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;
}
}
------------------------------------------------------------------------------------------------------------------------------
5.5. free管理
5.5.1. 将free的chunk块放入fastbinsY中
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
)
{}
------------------------------------------------------------------------------------------------------------------------------
5.5.2. 放入unsortedbin,前项和后项合并
对free的chunk在放入unsortedbin后,做了如下操作:
A:通过当前free的chunk找到与之相邻的下一个chunk块,即nextchunk,并对nextchunk对应的inuse位进行清0(clear_inuse_bit_at_offset(nextchunk, 0);),设置当前chunk的inuse位为1(set_head(p, size | PREV_INUSE);),设置nextchunk对应的prev_size为当前chunk的值。这样在真正free nextchunk时,可通过inuse为0和prev_size找到与之相应的前一个块进行合并。
clear_inuse_bit_at_offset(nextchunk, 0);
/* consolidate backward */
/*! 后向合并将两个相邻的chunk块合成一个 */
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) {
/* get and clear inuse bit */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
/* consolidate forward */
/*! 前向合并将多个相邻的chunk块合并成一个 */
if (!nextinuse) {
unlink_chunk (av, nextchunk);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0); //置nextchunk inuse位为0,为后向合并做准备
/*
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 (__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); //设置当前inuse位
set_foot(p, size); //设置nextchunk的prev_size为当前chunk的大小
check_free_chunk(av, p);
}
从上可知,free的chunk会直接放入到unsortedbin中,在前向和后向合并后,会将合并后的大chunk块也放入到unsortedbin中。
------------------------------------------------------------------------------------------------------------------------------
5.5.3. 将free的chunk还给操作系统
#define FASTBIN_CONSOLIDATION_THRESHOLD (65536UL) //64Kb
if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
if (atomic_load_relaxed (&av->have_fastchunks))
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);
#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);
}
}
只有当free的chunk size大于FASTBIN_CONSOLIDATION_THRESHOLD(64Kb)时,才会将内存直接还给操作系统。(注:size的大小不仅仅是用户free的chunk的大小,也会是前向和后向合并后的chunk大小)
malloc_consolidate会将fastbinsY中的所有chunk块与unsortedbin进行合并,合并后由systrim或heap_trim将其还给操作系统。