堆内存(2) ——分配入口__lib_malloc

__libc_malloc

malloc的入口函数为__libc_malloc其流程图如下

首先遍历fastbinsmall 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_aliasmalloc映射到__libc_malloc,在调用malloc的时候实际上是调用了__lib_malloc

strong_alias (__libc_malloc, __malloc) strong_alias (__libc_malloc, malloc)

preview

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_initfrom_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中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值