Glibc内存管理--ptmalloc2源代码分析(二十二)

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

 遍历全局分配区链表,尝试对当前遍历中的分配区加锁,如果对分配区加锁成功,将该分配区加入线程私有实例中并返回该分配区。如果 retriedtrue ,意味着这是第二次遍历全局分配区链表,并且获得了全局锁 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_heapmalloc_state 实例后的内存可以分配给用户使用, ptr 指向存储 malloc_state 实例后的空闲内存,对 ptr 按照 2*SZ_SIZE 对齐后,将 ptr 赋值给分配区的 top chunk ,也就是说把 sub_heap 中整个空闲内存块作为 top chunk ,然后设置 top chunksize ,并标识 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;
}
 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本文通过Glibc的内存暴增问题,主要介绍了系统的内存管理问题,具体如下: 目录 1. 问题 2. 基础知识 2.1 X86平台Linux进程内存布局 2.1.1 32位模式下进程内存经典布局 2.1.2 32位模式下进程默认内存布局 2.1.3 64位模式下进程内存布局 2.2 操作系统内存分配的相关函数 2.2.1 Heap操作相关函数 2.2.2 Mmap映射区域操作相关函数 3. 概述 3.1 内存管理一般性描述 3.1.1 内存管理的方法 3.1.2 内存管理器的设计目标 3.1.3 常见C内存管理程序 3.2 Ptmalloc内存管理概述 3.2.1 简介 3.2.2 内存管理的设计假设 3.2.3 内存管理数据结构概述 3.2.4 内存分配概述 3.2.5 内存回收概述 3.2.6 配置选项概述 3.2.7 使用注意事项 4. 问题分析及解决 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 分配区的初始化 5.4 配置选项 5.5 Ptmalloc的初始化 5.5.1 Ptmalloc未初始化时分配/释放内存 5.5.2 ptmalloc_init()函数 5.5.3 ptmalloc_lock_all(),ptmalloc_unlock_all(),ptmalloc_unlock_all2() 5.6 多分配区支持 5.6.1 Heap_info 5.6.2 获取分配区 5.6.3 Arena_get2() 5.6.4 _int_new_arena() 5.6.5 New_heap() 5.6.6 get_free_list()和reused_arena() 5.6.7 grow_heap(),shrink_heap(),delete_heap(),heap_trim() 5.7 内存分配malloc 5.7.1 public_mALLOc() 5.7.2 _int_malloc() 5.8 内存释放free 5.8.1 Public_fREe() 5.8.2 _int_free() 5.8.3 sYSTRIm()和munmap_chunk(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值