ptmalloc源码分析 - _int_malloc函数之fastbins(06)

本文详细解析了_int_malloc内存分配的核心函数,涉及bins的管理、chunk结构、内存分配前的检查、request2size转换以及fastbins的原子操作。重点介绍了fastbins的高效缓存策略和多线程同步方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、_int_malloc内存分配的核心函数

二、bins的管理和chunk的结构

三、_int_malloc函数分配前的两个检查

四、request2size用户请求分配大小转chunksize

五、REMOVE_FB原子实现fastbins的链表操作

六、fastbins的具体分配实现


一、_int_malloc内存分配的核心函数


前几章节,我们主要讲解了状态机malloc_state、内存组织单元malloc_chunk、分配区Arena三个概念。此处有必要再次再次加强这几个概念的理解:

  • 状态机:是指malloc_state这个数据结构,该结构用来管理分配区下面的内存组织结构,例如:fastbins、smallbins、largebins等。通过状态机,我们就能找到对应的内存应该在那个bins上分配
  • 内存组织单元:是指malloc_chunk这个数据结构,该结构主要管理每一次分配的内存,chunk分为数据记录区和数据区,通过chunk我们也能将不同的chunk进行双向链表放置到不同的bins上
  • 分配区:分配区分为主分配区和非主分配区,主要为了避免多线程争抢。

内存是如何分配的,以及如何将分配的内存关联到malloc_chunk,然后放置到malloc_state的状态机上,则是由_int_malloc函数实现的。

我们继续回到入口函数__libc_malloc :

  • 首选通过arena_get函数,获取一个分配区,得到ar_ptr的值(也是当前分配区状态机malloc_state)
  • 然后调用_int_malloc函数,传入ar_ptr和bytes参数,最终得到victim值,即malloc_chunk这个结构
  • 如果victim的值为NULL,分配失败,则调用arean_get_retry重新获取一个新的分配区,并重新执行_int_malloc函数
  /* 获取一个分配区,分配区分为主分配区和线程分配区 ,并加锁*/
  arena_get (ar_ptr, bytes);

  /* 执行malloc的分配操作 */
  victim = _int_malloc (ar_ptr, bytes);
  /* Retry with another arena only if we were able to find a usable arena
     before. 如果执行分配失败,则再执行一遍 */
  if (!victim && ar_ptr != NULL)
    {
      LIBC_PROBE (memory_malloc_retry, 1, bytes);
      ar_ptr = arena_get_retry (ar_ptr, bytes); //重新获取分配区
      victim = _int_malloc (ar_ptr, bytes);
    }

  /* 解除锁;arena_get函数里面,如果分配区能够取到,则会上锁 */
  if (ar_ptr != NULL)
    __libc_lock_unlock (ar_ptr->mutex);

  assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
          ar_ptr == arena_for_chunk (mem2chunk (victim)));
  return victim;
}

二、bins的管理和chunk的结构


这一节还是要继续回顾一下bins的管理方式和chunk的结构。这个比较重要,后续会贯穿整个_int_malloc的实现。

bins的实现

在malloc_state状态机里面,有两个结构对象:fastbinsY和bins

fastbinsY:fast bins是bins的高速缓冲区,大约有10个定长队列。当用户释放一块不大于max_fast(默认值64)的chunk(一般小内存)的时候,会默认会被放到fast bins上。

bins:主要管理small bins、large bins、unstored bins,在一个数组上,通过数组下标和步长方式来区分不同类型的bins

 

chunk实现:

 malloc_chunk结构主要管理了chunk的大小,双向链表的指针以及空闲链表的指针等。通过chunk结构也能获取到实际存储的数据区域。

三、_int_malloc函数分配前的两个检查


_int_malloc入参mstate av和size_t bytes,即分配区状态机结构和内存分配的大小。

分配前需要做两个检查:

  • 检查bytes是否在合法区间内,最大小于<2147483647

  • 如果分配区结构为空,则调用sysmalloc进行再次分配

/**
 * malloc的核心分配函数
 * av:分配区
 * bytes:内存的大小
 */
static void *
_int_malloc (mstate av, size_t bytes)
{

........

  //检查bytes是否在合法区间内,最大小于<2147483647
  if (!checked_request2size (bytes, &nb))
    {
      __set_errno (ENOMEM);
      return NULL;
    }

  /* There are no usable arenas.  Fall back to sysmalloc to get a chunk from
     mmap.  */
  /* 如果分配区结构为空,则调用sysmalloc进行再次分配 */
  if (__glibc_unlikely (av == NULL))
    {
      void *p = sysmalloc (nb, av);
      if (p != NULL)
	alloc_perturb (p, bytes);
      return p;
    }

四、request2size用户请求分配大小转chunksize


checked_request2size函数不仅检测每次内存分配的大小是否在合法区间内(<2147483647),还调用request2size函数,将用户申请的内存大小bytes转成nb字段。

request2size主要逻辑是最小的chunk大小为MINISIZE(64位系统32字节/32位系统16字节),SIZE_SZ是一个字段的大小(64位8字节/32位4字节),MALLOC_ALIGN_MASK为对齐掩码(64位 16-1=15;32位 8-1=7)

#define request2size(req)                                         \
  (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             \
   MINSIZE :                                                      \
   ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

//-----------64位系统
#define request2size(req)   (((req) + 8 + 15 < 32)  ?  32 : ((req) + 8 + 15) & ~~
15)
//-----------对齐的chunk值,64位系统从32字节开始,步长16开始递进
1-24 -> 32
25-40 -> 48
41-56 -> 64
57-72 -> 80

五、REMOVE_FB原子实现fastbins的链表操作


定义了一个REMOVE_FB宏函数,该函数主要目的: 原子操作,从fast bins链表上,弹出一个chunk

因为fastbinsY是一个数组,数组上挂载malloc_chunk是双向链表,当多线程进行同时操作链表的时候就会冲突,需要进行原子操作

核心看该函数:define catomic_compare_and_exchange_val_acq(mem, newval, oldval),mem指向newval,并且返回oldval的值

/*
 * 该函数就是原子操作,从fast bins链表上,弹出一个chunk
 *
 * 核心看该函数:
 * define catomic_compare_and_exchange_val_acq(mem, newval, oldval)
 * mem指向newval,并且返回oldval的值
 * */
#define REMOVE_FB(fb, victim, pp)			\
  do							\
    {							\
      victim = pp;					\
      if (victim == NULL)				\
	break;						\
    }							\
  while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) \
	 != victim);	

六、fastbins的具体分配实现


fastbins放置在malloc_state-> fastbinsY数组上,是高速缓冲区(主要用于小内存的快速分配和释放),大约有10个定长队列。

当用户释放一块不大于max_fast(默认值64)的chunk(一般小内存)的时候,会默认会被放到fast bins上。

_int_malloc函数中,先看申请的内存大小nb是否符合fast bins的限制,符合的话,首先进入fast bin的分配。

malloc_state-> fastbinsY数组为小内存块分配的空闲链表,如果该空闲链表为空,则无法分配chunk,需要继续往下走流程。

  if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
    {
      idx = fastbin_index (nb); //找到fastbin的数组下标idx
      mfastbinptr *fb = &fastbin (av, idx); //av->fastbinsY[idx]
      mchunkptr pp;
      victim = *fb; //vitctim是malloc_chunk结构

      /* 如果在fastbin上能够找到空闲的小于64b的chunk,则使用该chunk;
       * 如果在fastbin上没有合适的chunk,则去下面的small bins上去创建一个chunk */
      if (victim != NULL)
	{
	  if (SINGLE_THREAD_P)
	    *fb = victim->fd; //单线程
	  else
	    REMOVE_FB (fb, pp, victim); //设置 原子操作,将chunk从fast bins上摘下来,多线程的时候会争抢

	  /* REMOVE_FB是原子操作,所以这里需要继续判断一下,是否摘成功了,victim是否有值*/
	  if (__glibc_likely (victim != NULL))
	    {
	      size_t victim_idx = fastbin_index (chunksize (victim));
	      if (__builtin_expect (victim_idx != idx, 0))
		malloc_printerr ("malloc(): memory corruption (fast)");
	      check_remalloced_chunk (av, victim, nb);

..........................简.............

	      void *p = chunk2mem (victim); //通过chunk结构,获取data数据部分的指针地址
	      alloc_perturb (p, bytes); //初始化内存块
	      return p;
	    }
	}
    }

下一章节,我们继续看_int_malloc函数里面的smallbins、unsorted bin实现

<think>好的,我需要帮助用户找到`malloc_trim`函数的源码实现。首先,用户提供的引用内容涉及到Linux内存分配器,特别是ptmalloc2,这可能与glibc的实现有关。 根据引用[1],Linux使用的是ptmalloc2,它支持多线程,这提示我`malloc_trim`可能属于ptmalloc的一部分,而ptmalloc的源码包含在glibc中。所以,我需要定位到glibc的malloc目录下的相关文件,比如`malloc.c`或`arena.c`。 接下来,用户提到的引用[2]和[3]可能与调试或文档相关,但关键点还是源码的位置。搜索glibc的源码仓库,查找`malloc_trim`函数。通常,内存释放和整理函数会在负责arena管理的文件中实现,可能在`malloc/malloc.c`中找到该函数的定义。 另外,`malloc_trim`的作用是释放堆内存给操作系统,这可能涉及到`mtrim`相关的内部函数,比如`mtrim`。需要检查glibc源码中的这部分逻辑,确认其如何调用系统函数如`madvise`或`brk`来释放内存。 最后,确保引用正确的源码文件和版本,比如glibc 2.35中的实现,并指导用户如何查找和验证这些信息,比如通过在线代码库或下载源码包。</think>关于`malloc_trim`函数的源码实现,以下是关键分析步骤: 1. **源码位置** `malloc_trim`定义在glibc的**malloc/malloc.c**文件中[^1],其核心实现为: ```c int __malloc_trim(size_t s) { int result = 0; // 遍历所有arena并调用mtrim释放内存 mstate ar_ptr = &main_arena; do { (void) mutex_lock(&ar_ptr->mutex); result |= mtrim(ar_ptr, s); (void) mutex_unlock(&ar_ptr->mutex); ar_ptr = ar_ptr->next; } while (ar_ptr != &main_arena); return result; } ``` 2. **底层调用链** 函数通过`mtrim`→`heap_trim`→`delete_heap`最终调用系统级内存释放函数: ```c static int mtrim(mstate av, size_t s) { mchunkptr tp = av->top; // 获取当前arena的top chunk return heap_trim(av->system_mem, s); // 触发堆内存修剪 } ``` 在`heap_trim`中会通过`madvise(MADV_DONTNEED)`或`sbrk`系统调用将空闲内存返还给操作系统[^2]。 3. **关键参数** - `s`参数表示希望保留的堆顶最小空间 - 返回值表示是否成功释放内存 4. **实现限制** 该函数仅释放**通过sbrk分配的main arena内存**,而通过mmap分配的arena无法通过此函数释放[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值