__libc_malloc
malloc的入口函数为__libc_malloc
其流程图如下
首先遍历fastbin
和small bin
,希望从中分到chunk。如果没有找到堆块,接下来就开始合并到fastbin
中的堆块,合并完添加到unsorted bin
中,然后进入到大循环。
进入到大循环的路径有两个:1、请求堆块为large chunk。2、small bin中对应的bin为空
1、将unsorted bin里面所有的chunk都添加到small bin和large bin里面去。走到大循环这一步,说明前面的fastbin已经被合并过并且全部添加到了unsorted bin里面,所以这个时候fastbin是空的!但是在添加的过程中,如果遇到unsorted chunk的大小正好满足用户请求的大小,则直接退出添加过程,并将当前遍历到的chunk返回给用户。last remainder是unsorted chunk整理完了到最后才处理的,满足nb为small chunk这一条件即可从last remainder中分割一块返回给用户,剩下的last remainder继续加入到已经被清空的unsorted bin里面。到了这一步,small chunk请求应该要得到满足了,如果没有得到满足,说明需要到更大的bin里面分配。总之,大循环的第一个功能就是把unsorted chunk重新添加到各个bin,分配堆块只是它顺手完成的工作,当然能分配固然是好事,这样可以省好多事哈哈哈哈!
2、如果用户请求的是large chunk,那么large chunk的分配工作也是在大循环里面完成的。处理完unsorted bin紧接着就是处理large bin。
3、走到第三步说明严格按照用户请求的大小来分配堆块是不可行的,因此要向更大的bin申请堆块。这一步是通过扫描arena里面的binmap来寻找的。
4、如果到这里还没分到堆块,说明所有的bin都没有合适的堆块可以分配,只能向top chunk求救了。如果top chunk大小满足条件可以分割,OK直接从top chunk上切一块下来,剩下的作为新的top chunk。但是如果top chunk太小满足不了请求,只能再回过头到fastbin里面看看还有没有机会了,所以接下来会通过检查arena的have_fastchunk字段来判断fastbin是否为空,如果fastbin不为空,哈哈哈说明还有救,可以继续调用malloc_consolidate函数合并fastbin到unsorted bin,再跳到第1步重新遍历。这里可能会有疑问,fastbin不是前面已经合并过了么,不应该为空么,怎么到这里又有了呢?我的理解是,对于线程堆,可能当前线程睡眠的时候又有其他线程释放堆块到fastbin,对于主线程堆可能就不存在这种情况。
5、还没分配到则进入sysmalloc
glibc 通过别名机制strong_alias
从malloc
映射到__libc_malloc
,在调用malloc的时候实际上是调用了__lib_malloc
strong_alias (__libc_malloc, __malloc) strong_alias (__libc_malloc, malloc)
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
_Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
"PTRDIFF_MAX is not more than half of SIZE_MAX");
//以原子的方式进行初始化
//第一次调用malloc函数时在这会初始化堆内存
//__malloc_hook 值是 malloc_hook_ini ,初始化ptmalloc
//atomic_forced_read 汇编语句,原子读操作,读取__malloc_hook
//weak_variable 相当于属性赋值操作attribute,将malloc_hook_ini转到__malloc_hook
//malloc_hook_ini 中调用的是ptmalloc_init()函数,初始化ptmalloc,并将__malloc_hook转为空值防止多次初始化
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
glibc通过atomic_forced_read
原子初始化操作调用__malloc_hook
在转到__malloc_hook
最终调用ptmalloc_init
进行初始化操作。
ptmalloc_init
static void
ptmalloc_init (void)
{
//检查全局变量__malloc_initialized是否大于等于0保证只初始化一次
if (__malloc_initialized >= 0)
return;
__malloc_initialized = 0;
//不在默认的命名空间时不会使用brk
//静态链接动态库也不会
#ifdef SHARED
/* In case this libc copy is in a non-default namespace, never use brk.
Likewise if dlopened from statically linked program. */
Dl_info di;
struct link_map *l;
if (_dl_open_hook != NULL
|| (_dl_addr (ptmalloc_init, &di, &l, NULL) != 0
&& l->l_ns != LM_ID_BASE))
__morecore = __failing_morecore;
#endif
thread_arena = &main_arena;
//初始化malloc_state
malloc_init_state (&main_arena);
首先对全局变量__malloc_initialized
的值进行检查,保证只初始化一次。如果内存为共享内存则不会使用brk
随后调用malloc_init_state
进行初始化操作。
初始化malloc_state
在第一次调用malloc时malloc_state
才会被初始化malloc_init_state
的作用是初始化malloc_state
结构体,可通过ptmalloc_init
和from_int_new_arena
进=入
static void
malloc_init_state (mstate av)
{
int i;
mbinptr bin;
//初始化分配区的bins结构NBINS的值为128
//#define bin_at(m, i) \
//(mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \
// - offsetof (struct malloc_chunk, fd))
//
/* Establish circular links for normal bins */
for (i = 1; i < NBINS; ++i)
{
bin = bin_at (av, i);
bin->fd = bin->bk = bin;
}
#if MORECORE_CONTIGUOUS
if (av != &main_arena)
#endif
//首先设置不连续标志
set_noncontiguous (av);
if (av == &main_arena)
//设置DEFAULT_MXFAST为64所以fastbin的大小为16到128
set_max_fast (DEFAULT_MXFAST);
//将该分区的hava_fastchunks设置为否
atomic_store_relaxed (&av->have_fastchunks, false);
//初始化top chunk,至此malloc_state所有成员初始化完成,top chunk的位置在第一个bin[0]的前一个块
av->top = initial_top (av);
}
这里要啊注意的是fastbin
的大小被设置为16~128如果需要修改最大的fast bin
可使用mallopt
设置但最大值不可大于160B
malloc_check_init
当malloc_state
结构初始化完成后会初始化环境变量和check
void
__malloc_check_init (void)
{
using_malloc_checking = 1;
__malloc_hook = malloc_check;
__free_hook = free_check;
__realloc_hook = realloc_check;
__memalign_hook = memalign_check;
}
该函数中将__malloc_hook
赋值为 malloc_check
这样的话第二次调用malloc就不会再去初始化malloc state
结构体了。
TCACHE技术
回到__lib_malloc
中在初始化malloc结构后,会判断是否开启了tcache技术, tcache是libc2.26之后引进的一种新机制,类似于fastbin一样的东西,每条链上最多可以有 7 个 chunk,free的时候当tcache满了才放入fastbin,unsorted bin,malloc的时候优先去tcache找。其相关结构体如下
/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
tcache_perthread_struct
结构体是用来管理tcache链表的。其中的count
是一个字节数组(共64个字节,对应64个tcache链表),其中每一个字节表示的是tcache每一个链表中有多少个元素。entries
是一个指针数组(共64个元素,对应64个tcache链表,因此 tcache bin中最大为0x400字节),每一个指针指向的是对应tcache_entry
结构体的地址。
该链表与fastbin链表的异同点在于:
- tcachebin和fastbin都是通过chunk的fd字段来作为链表的指针
- tcachebin中的链表指针指向的下一个chunk的
fd
字段,fastbin中的链表指针指向的是下一个chunk的prev_size
字段
在_int_free
中,最开始就先检查chunk的size是否落在了tcache的范围内,且对应的tcache未满,将其放入tcache中。
在_int_malloc
中,
-
如果从fastbin中取出了一个块,那么会把剩余的块放入tcache中直至填满tcache(smallbin中也是一样)
-
如果进入了unsortedbin,且chunk的size和当前申请的大小精确匹配,那么在tcache未满的情况下会将其放入到tcachebin中