目录
在《ptmalloc源码分析 - 分配区状态机malloc_state(02)》中,我们介绍过ptmalloc为了解决多线程同时并发争抢的时候,会分为主分配区和非主分配区。
我们可以看一下一个线程调用malloc的时候的流程以及分配区的状态:
通过主分配区和非主分配区,就可以解决多线程的冲突问题了。
- /**
- * 全局malloc状态管理
- */
- struct malloc_state
- {
- .......
-
- /* 分配区全局链表:分配区链表,主分配区放头部,新加入的分配区放main_arean.next 位置 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;
-
- /* freelist的状态,0-空闲 1-正在使用中,关联的线程数 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;
-
- .....
- };
malloc_state是分配区的数据结构,起到一个状态机的作用,记录分配区的重要信息。
获取分配区的主函数是arena_get(arena.c文件中),该函数主要从thread_arena(当前线程的私有变量),获取一个分配区。如果获取到了,则加锁,进行后续的操作;如果没有获取到,线程第一次获取分配区,则调用arena_get2函数进行分配区的初始化。
这里有两个重要的变量,贯穿整个分配区:
在第一次pcmalloc_init的时候,就将thread_arena设置成main_arena,意味着进程的主线程对应主分配区,然后再对主分配区进行初始化操作。
- /*
- * ptmalloc_init 初始化过程
- */
- static void ptmalloc_init(void) {
- /**
- * 1. 判断是否已经初始化,如果初始化过了,则不再执行;
- * 2. 如果等于0,则正在初始化,如果等于1,则初始化完成
- */
- if (__malloc_initialized >= 0)
- return;
-
- __malloc_initialized = 0;
-
- ........
-
- /**
- * 1. main_arena为主分配区域
- * 2. malloc_init_state 初始化主分配区数据
- * 3. 将主线程的thread_arena值设置为main_arena
- */
- thread_arena = &main_arena;
-
- malloc_init_state(&main_arena);
-
- ......
- /* 初始化完毕,则设置为1 */
- __malloc_initialized = 1;
- }
arena_get整个流程是这样的:
先从私有变量中thread_arena尝试获取分配区,不同线程都会设置自己的分配区
如果分配区存在,则加锁进行处理,直接返回当前分配区
如果分配区不存在,则调用arena_get2函数,从空闲链表或者新创建分配区
thread_arena = &main_arena; 进程的主线程对应的是主分配区
如果当前线程没有设置过分配区,则通过arena_get2进行分配区的申请
- /**
- * 1. 先从私有变量中thread_arena尝试获取分配区,不同线程都会设置自己的分配区
- * 2. 如果分配区存在,则加锁进行处理,直接返回当前分配区
- * 3. 如果分配区不存在,则调用arena_get2函数,从空闲链表或者新创建分配区
- * 4. thread_arena = &main_arena; 进程的主线程对应的是主分配区
- * 5. 如果当前线程没有设置过分配区,则通过arena_get2进行分配区的申请
- */
- #define arena_get(ptr, size) do { \
- ptr = thread_arena; \
- arena_lock (ptr, size); \
- } while (0)
-
- #define arena_lock(ptr, size) do { \
- if (ptr) \
- __libc_lock_lock (ptr->mutex); \
- else \
- ptr = arena_get2 ((size), NULL); \
- } while (0)
如果线程是第一次申请分配区,这调用arena_get2函数,该函数也在arena.c文件中,该函数主要实现了三个功能:
get_free_list:从空闲链表中获取一个分配区,如果空闲链表中有该分配区,则直接使用,返回结果
_int_new_arena:去创建一个新的分配区,也就是一个malloc_state结构的对象,并且挂载到main_arena.next链表上面
reused_arena:如果分配区已经分配满了(分配区有个数上限),则需要循环等待其中一个分配区解锁
分配区个数:多少个分配区,根据系统来决定,一个进程最多能分配的arena个数在64位下是8 * core + 1,32位下是2 * core + 1个;arena 对于32位系统,数量最多为核心数量2倍,64位则最多为核心数量8倍,可以用来保证多线程的堆空间分配的高效性。
当arena满了之后就不再创建而是与其他arena共享一个arena,方法为依次给各个arena上锁(查看是否有其他线程正在使用该arena),如果上锁成功(没有其他线程正在使用),则使用该arena,之后一直使用这个arena,如果无法使用则阻塞等待。
- /**
- * 获取一个分配区,如果有空闲的,则走空闲链表;没有则创建新的分配区;分配区满了,则等待释放
- */
- static mstate arena_get2(size_t size, mstate avoid_arena) {
- mstate a;
-
- static size_t narenas_limit;
-
- /* 从空闲链表上获取一个mstate的分配区 */
- a = get_free_list();
-
- /* 如果空闲链表为NULL,则创建一个新的arean分配区 */
- if (a == NULL) {
- /* Nothing immediately available, so generate a new arena. */
- /* 多少个分配区,根据系统来决定,一个进程最多能分配的arena个数在64位下是8 * core,32位下是2 * core个
- * arena 对于32位系统,数量最多为核心数量2倍,64位则最多为核心数量8倍,可以用来保证多线程的堆空间分配的高效性。
- * 主要存储了较高层次的一些信息。有一个main_arena,是由主线程创建的,thread_arena则为各线程创建的,
- * 当arena满了之后就不再创建而是与其他arena共享一个arena,方法为依次给各个arena上锁(查看是否有其他线程正在使用该arena),
- * 如果上锁成功(没有其他线程正在使用),则使用该arena,之后一直使用这个arena,如果无法使用则阻塞等待。
- * */
- if (narenas_limit == 0) {
- if (mp_.arena_max != 0)
- narenas_limit = mp_.arena_max;
- else if (narenas > mp_.arena_test) {
- int n = __get_nprocs();
-
- if (n >= 1)
- narenas_limit = NARENAS_FROM_NCORES(n);
- else
- /* We have no information about the system. Assume two
- cores. */
- narenas_limit = NARENAS_FROM_NCORES(2); //默认是核数的两倍
- }
- }
- repeat: ;
- size_t n = narenas; //narenas=1
- /* NB: the following depends on the fact that (size_t)0 - 1 is a
- very large number and that the underflow is OK. If arena_max
- is set the value of arena_test is irrelevant. If arena_test
- is set but narenas is not yet larger or equal to arena_test
- narenas_limit is 0. There is no possibility for narenas to
- be too big for the test to always fail since there is not
- enough address space to create that many arenas. */
- /* */
- if (__glibc_unlikely(n <= narenas_limit - 1)) {
- if (catomic_compare_and_exchange_bool_acq(&narenas, n + 1, n))
- goto repeat;
- a = _int_new_arena(size); //创建一个新的分配区
- if (__glibc_unlikely(a == NULL))
- catomic_decrement(&narenas);
- } else
- a = reused_arena(avoid_arena); //复用默认分区
- }
- return a;
- }
通过全局变量free_list保存空闲链表。如果空闲链表为空,则直接返回空的值,如果不为空,则调整free_list的变量值为free_list->next。将attached_threads的值设置成1,说明已经有线程绑定该分配区进行使用了。最后需要将thread_arena的线程私有变量,设置成分配区。
remove_from_free_list函数:主要是移除free_list,直接操作next_free的指针即可
- /**
- * 从FreeList上获取一个分配区
- */
- /* Remove an arena from free_list. */
- static mstate get_free_list(void) {
- mstate replaced_arena = thread_arena; //获取当前线程分配区
- /* free_list 全局变量 */
- mstate result = free_list; //当前空闲的分配区
- if (result != NULL) {
- __libc_lock_lock(free_list_lock); //加锁
- result = free_list; //再次获取free_list
- if (result != NULL) {
- free_list = result->next_free; //移动free_list
-
- /* The arena will be attached to this thread. */
- assert(result->attached_threads == 0);
- result->attached_threads = 1; //修改分配区的线程绑定个数
-
- detach_arena(replaced_arena);
- }
- __libc_lock_unlock(free_list_lock); //解除锁
-
- /* 分配区加锁,并将thread_arena设置为result */
- if (result != NULL) {
- LIBC_PROBE(memory_arena_reuse_free_list, 1, result);
- __libc_lock_lock(result->mutex);
- thread_arena = result; //将线程的分配区设置为result
- }
- }
-
- return result;
- }
-
- /* Remove the arena from the free list (if it is present).
- free_list_lock must have been acquired by the caller.
- 移动链表地址,移除free_list上的分配区结构*/
- static void remove_from_free_list(mstate arena) {
- mstate *previous = &free_list;
- for (mstate p = free_list; p != NULL; p = p->next_free) {
- assert(p->attached_threads == 0);
- if (p == arena) {
- /* Remove the requested arena from the list. */
- *previous = p->next_free;
- break;
- } else
- previous = &p->next_free;
- }
- }
_int_new_arena函数主要是创建一个新的分配区,该分配区主要是非主分配区类型。主分配区在ptmalloc_init中初始化,并且设置了全局变量main_arena的值。
- /**
- * 初始化一个新的分配区arena
- * 该函数主要创建:非主分配区
- * 主分配区在ptmalloc_init中初始化,并且设置了全局变量main_arena的值
- */
- static mstate _int_new_arena(size_t size) {
- mstate a;
- heap_info *h;
- char *ptr;
- unsigned long misalign;
-
- /* 分配一个heap_info,用于记录堆的信息,非主分配区一般都是通过MMAP向系统申请内存;非主分配区申请后,是不能被销毁的 */
- 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;
- }
- a = h->ar_ptr = (mstate)(h + 1); //heap_info->ar_ptr的值设置成mstate的分配区状态机的数据结构
-
- malloc_init_state(a); //初始化mstate
- a->attached_threads = 1; //设置进程关联个数
- /*a->next = NULL;*/
- a->system_mem = a->max_system_mem = h->size;
-
- /* 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);
-
- LIBC_PROBE(memory_arena_new, 2, a, size);
- mstate replaced_arena = thread_arena;
- thread_arena = a; //将当前线程设置mstate
- __libc_lock_init(a->mutex); //初始化分配区锁
-
- __libc_lock_lock(list_lock); //加上分配区锁
-
- /* 将新的分配区加入到全局链表上,新申请的分配区都会放入主分配区的下一个位置*/
- /* Add the new arena to the global list. */
- a->next = main_arena.next;
- /* FIXME: The barrier is an attempt to synchronize with read access
- in reused_arena, which does not acquire list_lock while
- traversing the list. */
- atomic_write_barrier();
- main_arena.next = a;
- __libc_lock_unlock(list_lock);
-
- /* 调整attached_threads状态*/
- __libc_lock_lock(free_list_lock);
- detach_arena(replaced_arena);
- __libc_lock_unlock(free_list_lock);
-
-
- __malloc_fork_lock_parent. */
-
- __libc_lock_lock(a->mutex); //解除分配区锁
-
- return a;
- }
-
- /* Remove the arena from the free list (if it is present).
- free_list_lock must have been acquired by the caller.
- 移动链表地址,移除free_list上的分配区结构*/
- static void remove_from_free_list(mstate arena) {
- mstate *previous = &free_list;
- for (mstate p = free_list; p != NULL; p = p->next_free) {
- assert(p->attached_threads == 0);
- if (p == arena) {
- /* Remove the requested arena from the list. */
- *previous = p->next_free;
- break;
- } else
- previous = &p->next_free;
- }
- }
如果分配区全部处于忙碌中,则通过遍历方式,尝试没有加锁的分配区进行分配操作。如果得到一个没有加锁的分配区,则attached_threads关联的线程数,并将thread_arena设置到当前的分配区上。这样就实现了多线程环境下,分配区的重复利用。
- /* Lock and return an arena that can be reused for memory allocation.
- Avoid AVOID_ARENA as we have already failed to allocate memory in
- it and it is currently locked.
- 如果分配区全部处于忙碌中,则通过遍历方式,尝试没有加锁的分配区进行分配操作
- */
- static mstate reused_arena(mstate avoid_arena) {
- mstate result;
- /* FIXME: Access to next_to_use suffers from data races. */
- static mstate next_to_use;
- if (next_to_use == NULL)
- next_to_use = &main_arena;
-
- /* Iterate over all arenas (including those linked from
- free_list). 循环遍历整个分配区链表 */
- result = next_to_use;
- do {
- if (!__libc_lock_trylock(result->mutex)) //寻找一个不能锁定的分配区
- goto out;
-
- /* FIXME: This is a data race, see _int_new_arena. */
- result = result->next;
- } while (result != next_to_use);
-
- /* Avoid AVOID_ARENA as we have already failed to allocate memory
- in that arena and it is currently locked. */
- if (result == avoid_arena)
- result = result->next;
-
- /* No arena available without contention. Wait for the next in line. */
- LIBC_PROBE(memory_arena_reuse_wait, 3, &result->mutex, result, avoid_arena);
- __libc_lock_lock(result->mutex);
-
- /* 跳转操作 */
- out:
- /* Attach the arena to the current thread. */
- {
- /* Update the arena thread attachment counters. */
- mstate replaced_arena = thread_arena;
- __libc_lock_lock(free_list_lock); //加锁
- detach_arena(replaced_arena);
-
- /* We may have picked up an arena on the free list. We need to
- preserve the invariant that no arena on the free list has a
- positive attached_threads counter (otherwise,
- arena_thread_freeres cannot use the counter to determine if the
- arena needs to be put on the free list). We unconditionally
- remove the selected arena from the free list. The caller of
- reused_arena checked the free list and observed it to be empty,
- so the list is very short. */
- remove_from_free_list(result); //从free list移动除,多个线程共用
-
- ++result->attached_threads; //线程引用数量+1
-
- __libc_lock_unlock(free_list_lock); //解锁
- }
-
- LIBC_PROBE(memory_arena_reuse, 2, result, avoid_arena);
- thread_arena = result; //设置线程
- next_to_use = result->next; //貌似没意义的一行代码
-
- return result;
- }
非主分配区都是通过new_heap的方式,进行内存的申请和分配,下一章,我们重点讲解一下heap_info堆信息的结构以及与分配区的关系