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; }