5.6.3 Arena_get2()
在5.6.2
节中提到,
arena_get
宏尝试查看线程的私用实例中是否包含一个分配区,如果不存在分配区或是存在分配区,但对该分配区加锁失败,就会调用
arena_get2()
函数获得一个分配区,下面将分析
arena_get2()
函数的实现。
static mstate
internal_function
#if __STD_C
arena_get2(mstate a_tsd, size_t size)
#else
arena_get2(a_tsd, size) mstate a_tsd; size_t size;
#endif
{
mstate a;
#ifdef PER_THREAD
if ((a = get_free_list ()) == NULL
&& (a = reused_arena ()) == NULL)
/* Nothing immediately available, so generate a new arena. */
a = _int_new_arena(size);
如果开启了
PER_THREAD
优化,首先尝试从分配区的
free list
中获得一个分配区,分配区的
free list
是
从父线程(进程)中继承而来,如果
free list
中没有分配区,尝试重用已有的分配区,只有当分配区的数达到限制时才重用分配区,如果仍未获得可重用的分配区,创建一个新的分配区。
#else
if(!a_tsd)
a = a_tsd = &main_arena;
else {
a = a_tsd->next;
if(!a) {
/* This can only happen while initializing the new arena. */
(void)mutex_lock(&main_arena.mutex);
THREAD_STAT(++(main_arena.stat_lock_wait));
return &main_arena;
}
}
如果线程的私有实例中没有分配区,将主分配区作为候选分配区,如果线程私有实例中存在分配区,但不能获得该分配区的锁,将该分配区的下一个分配区作为候选分配区,如果候选分配区为空,意味着当前线程私用实例中的分配区正在初始化,还没有加入到全局的分配区链表中,这种情况下,只有主分配区可选了,等待获得主分配区的锁,如果获得住分配区的锁成功,返回主分配区。
/* Check the global, circularly linked list for available arenas. */
bool retried = false;
repeat:
do {
if(!mutex_trylock(&a->mutex)) {
if (retried)
(void)mutex_unlock(&list_lock);
THREAD_STAT(++(a->stat_lock_loop));
tsd_setspecific(arena_key, (Void_t *)a);
return a;
}
a = a->next;
} while(a != a_tsd);
遍历全局分配区链表,尝试对当前遍历中的分配区加锁,如果对分配区加锁成功,将该分配区加入线程私有实例中并返回该分配区。如果
retried
为
true
,意味着这是第二次遍历全局分配区链表,并且获得了全局锁
list_lock
,当对分配区加锁成功时,需要释放全局锁
list_lock
。
/* If not even the list_lock can be obtained, try again. This can
happen during `atfork', or for example on systems where thread
creation makes it temporarily impossible to obtain _any_
locks. */
if(!retried && mutex_trylock(&list_lock)) {
/* We will block to not run in a busy loop. */
(void)mutex_lock(&list_lock);
/* Since we blocked there might be an arena available now. */
retried = true;
a = a_tsd;
goto repeat;
}
由于在
atfork
时,父线程(进程)会对所有的分配区加锁,并对全局锁
list_lock
加锁,在有线程在创建子线程的情况下,当前线程是不能获得分配区的,所以在没有重试的情况下,先尝试获得全局锁
list_lock
,如果不能获得全局锁
list_lock
,阻塞在全局锁
list_lock
上,直到获得全局锁
list_lock
,也就是说当前已没有线程在创建子线程,然后再重新遍历全局分配区链表,尝试对分配区加锁,如果经过第二次尝试仍然未能获得一个分配区,只能创建一个新的非主分配区了。
/* Nothing immediately available, so generate a new arena. */
a = _int_new_arena(size);
(void)mutex_unlock(&list_lock);
通过前面的所有尝试都未能获得一个可用的分配区,只能创建一个新的非主分配区,执行到这里,可以确保获得了全局锁
list_lock
,在创建完新的分配区,并将分配区加入了全局分配区链表中以后,需要对全局锁
list_lock
解锁。
#endif
return a;
}
5.6.4 _int_new_arena()
_int_new_arena()
函数用于创建一个非主分配区,在
arena_get2()
函数中被调用,该函数的实现代码如下:
static mstate
_int_new_arena(size_t size)
{
mstate a;
heap_info *h;
char *ptr;
unsigned long misalign;
h = new_heap(size + (sizeof(*h) + sizeof(*a) + MALLOC_ALIGNMENT),
mp_.top_pad);
if(!h) {
/* Maybe size is too large to fit in a single heap. So, just try
to create a minimally-sized arena and let _int_malloc() attempt
to deal with the large request via mmap_chunk(). */
h = new_heap(sizeof(*h) + sizeof(*a) + MALLOC_ALIGNMENT, mp_.top_pad);
if(!h)
return 0;
}
对于一个新的非主分配区,至少包含一个
sub_heap
,每个非主分配区中都有相应的管理数据结构,每个非主分配区都有一个
heap_info
实例和
malloc_state
的实例,这两个实例都位于非主分配区的第一个
sub_heap
的开始部分,
malloc_state
实例紧接着
heap_info
实例。所以在创建非主分配区时,需要为管理数据结构分配额外的内存空间。
New_heap()
函数创建一个新的
sub_heap
,并返回
sub_heap
的指针。
a = h->ar_ptr = (mstate)(h+1);
malloc_init_state(a);
/*a->next = NULL;*/
a->system_mem = a->max_system_mem = h->size;
arena_mem += h->size;
在heap_info
实例后紧接着
malloc_state
实例,初始化
malloc_state
实例,更新该分配区所分配的内存大小的统计值。
#ifdef NO_THREADS
if((unsigned long)(mp_.mmapped_mem + arena_mem + main_arena.system_mem) >
mp_.max_total_mem)
mp_.max_total_mem = mp_.mmapped_mem + arena_mem + main_arena.system_mem;
#endif
/* Set up the top chunk, with proper alignment. */
ptr = (char *)(a + 1);
misalign = (unsigned long)chunk2mem(ptr) & MALLOC_ALIGN_MASK;
if (misalign > 0)
ptr += MALLOC_ALIGNMENT - misalign;
top(a) = (mchunkptr)ptr;
set_head(top(a), (((char*)h + h->size) - ptr) | PREV_INUSE);
在sub_heap
中
malloc_state
实例后的内存可以分配给用户使用,
ptr
指向存储
malloc_state
实例后的空闲内存,对
ptr
按照
2*SZ_SIZE
对齐后,将
ptr
赋值给分配区的
top chunk
,也就是说把
sub_heap
中整个空闲内存块作为
top chunk
,然后设置
top chunk
的
size
,并标识
top chunk
的前一个
chunk
为已处于分配状态。
tsd_setspecific(arena_key, (Void_t *)a);
mutex_init(&a->mutex);
(void)mutex_lock(&a->mutex);
将创建好的非主分配区加入线程的私有实例中,然后对非主分配区的锁进行初始化,并获得该锁。
#ifdef PER_THREAD
(void)mutex_lock(&list_lock);
#endif
/* Add the new arena to the global list. */
a->next = main_arena.next;
atomic_write_barrier ();
main_arena.next = a;
#ifdef PER_THREAD
++narenas;
(void)mutex_unlock(&list_lock);
#endif
将刚创建的非主分配区加入到分配区的全局链表中,如果开启了
PER_THREAD
优化,在
arena_get2()
函数中没有对全局锁
list_lock
加锁,这里修改全局分配区链表时需要获得全局锁
list_lock
。如果没有开启
PER_THREAD
优化,
arene_get2()
函数调用
_int_new_arena()
函数时已经获得了全局锁
list_lock
,所以对全局分配区链表的修改不用再加锁。
THREAD_STAT(++(a->stat_lock_loop));
return a;
}
分享到:
相关推荐
NULL 博文链接:https://mqzhuang.iteye.com/blog/1064966
Glibc内存管理 Ptmalloc2 源代码分析
glibc内存管理ptmalloc源代码分析-电子资料-高清PDF版-pdf打印版
简介133.2.2内存管理的设计假设 143.2.3内存管理数据结构概述 143.2.4内存分配概述 193.2.5内存回收概述 213.2.6配置选项概述 2
淘宝网的研发人员写的文档,对了解GNU C的内存分配机制有很大的帮助!
该文档详细分析了c库中的内存管理细节,可广大程序员做参考。
简介133.2.2内存管理的设计假设 143.2.3内存管理数据结构概述 143.2.4内存分配概述 193.2.5内存回收概述 213.2.6配置选项概述 2
清晰版,对内存分析,程序设计非常有帮助。适合进阶的。
5. 源代码分析 5.1 边界标记法 5.2 分箱式内存管理 5.2.1 Small bins 5.2.2 Large bins 5.2.3 Unsorted bin 5.2.4 Fast bins 5.3 核心结构体分析 5.3.1 malloc_state 5.3.2 Malloc_par 5.3.3 分配区的初始...
glibc内存管理ptmalloc源代码分析.pdf
源代码分析 glibc的ptmalloc 分析
glibc内存管理ptmalloc源代码分析4.pdf
学习自《glibc内存管理ptmalloc源代码分析》庄明强 著 部分资料参考自互联网 chunk 描述: 当用户通过malloc等函数申请空间时,实际上是从堆中分配内存 目前 Linux 标准发行版中使用的是 glibc 中的堆分配器:...