ptmalloc源码分析 - 内存组织单元malloc_chunk(03)

本文解析了ptmalloc中的核心数据结构malloc_chunk,介绍了其基础结构、使用与空闲状态,并阐述了mchunk_size字段的复用机制及宏操作。

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

目录

一、malloc_chunk的数据结构

1. malloc_chunk的基础结构

2. malloc_chunk使用时的状态

3. malloc_chunk空闲时的状态

二、mchunk_size的内存分配大小

三、mchunk_size的标记位的复用

四、malloc_chunk的宏函数操作


上一章讲解了ptmalloc的内存的分配器状态机malloc_state的实现。分配器状态机主要管理内存分配过程中的各种状态以及空闲内存的管理。

ptmalloc的最小内存组织单元是malloc_chunk的数据结构。通过malloc_chunk的数据结构,用于管理每次前端程序使用malloc函数调用所产生的在堆上分配的内存。

一、malloc_chunk的数据结构


1. malloc_chunk的基础结构


先来看一下malloc_chunk的数据结构:

  • mchunk_prev_size:该字段记录物理相邻的前一个chunk的大小(低地址chunk)。如果前一个chunk处于空闲,则该字段记录前一个chunk大小;如果前一个chunk已经被使用,则该字段空间可以被前一个chunk的用户数据空间复用
  • mchunk_size:该字段是chunk的大小。该字段的低三个比特位对 chunk 的大小没有影响,所以被复用为标志位.
  • fd和bk:当chunk空闲的时候,会放置到bins上双向链表管理。fd 指向下一个(非物理相邻)空闲的 chunk。bk 指向上一个(非物理相邻)空闲的 chunk。由于只有chunk空闲的时候,才会放置到bins上进行空闲管理,所以fd和bk占用的是用户数据区域user data
  • fd_nextsize和bk_nextsize:用于管理large块的时候的空闲chunk双向链表的管理。一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适 chunk 时挨个遍历,也是复用用户数据区域。large chunk的空间肯定装的下
struct malloc_chunk {

  INTERNAL_SIZE_T      mchunk_prev_size;  /*  Size of previous chunk (if free).  */

  INTERNAL_SIZE_T      mchunk_size;       /* 当前chunk的大小 Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

2. malloc_chunk使用时的状态


当前chunk如果被使用中,则有几个特征:

  • 复用相邻下一个nextchunk(高地址chunk)的mchunk_prev_size字段的数据空间。所以该chunk的内存空间为当前内存 + mchunk_prev_size字段空间
  • 由于chunk被使用中,所以不需要通过双向链表方式挂载到空闲bins上管理,所以fd和bk以及fd_nextsize和bk_nextsize不需要被使用
    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	    |             Size of previous chunk, if unallocated (P clear)  |
	    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	    |             Size of chunk, in bytes                     |A|M|P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	    |             User data starts here...                          .
	    .                                                               .
	    .             (malloc_usable_size() bytes)                      .
	    .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	    |             (size of chunk, but used for application data)    |
	    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	    |             Size of next chunk, in bytes                |A|0|1|
	    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

图解:

  • chunk-> 是当前内存组织单元的开始地址;
  • mem -> 是用户数据区user data的开始区域;
  • nextchunk-> 是下一个内存组织单元的开始地址

从上图中可以看到,chunk正在被使用中,所以没有fd/bk以及fd_nextsize/bk_nextsize的指针。

而chunk的用户内存区域(user data),除了本身内存区域之外,也将底部nextchunk的mchunk_prev_size指针数据部分给复用了进来,实现了内存空间的复用。

3. malloc_chunk空闲时的状态


当chunk为空闲的时候,chunk会被挂载的空闲链表bins上进行管理,所以空闲的chunk有几个特征

  • fd/bk以及fd_nextsize/bk_nextsize的指针地址,复用了用户数据区域(userdata)的空间
  • 下一个nextchunk的mchunk_prev_size值,记录了当前chunk的大小

    chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	    |             Size of previous chunk, if unallocated (P clear)  |
	    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    `head:' |             Size of chunk, in bytes                     |A|0|P|
      mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	    |             Forward pointer to next chunk in list             |
	    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	    |             Back pointer to previous chunk in list            |
	    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	    |             Unused space (may be 0 bytes long)                .
	    .                                                               .
	    .                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    `foot:' |             Size of chunk, in bytes                           |
	    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	    |             Size of next chunk, in bytes                |A|0|0|
	    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

从上图中可以看到,由于空闲chunk需要通过双向链表指针来管理,所以fd/bk的指针复用了用于内存存储区域。

二、mchunk_size的内存分配大小


最小的空间:mchunk_prev_size字段 + mchunk_size字段 + fd字段 + bk字段 所需要的空间。但是chunk的使用时只使用mchunk_prev_size字段 + mchunk_size字段,空闲的时候 fd字段 + bk字段(fd_nextsize/bk_nextsize)用用户空间来替代。所以chunk主要存储mchunk_prev_size/mchunk_size,为16字节(64位/32位系统=8字节)。

chunk的size:(chunk的mchunk_size字段空间SIZE_SZ + 用户数据区域)& 对齐字节(2*SIZE_SZ)   ;由于User空间可以复用下一个chunk的mchunk_prev_size,所以可以去除这个空间

chunk对齐规则:按照2*SIZE_SZ进行对齐,32位系统是8字节,64位系统是16字节。

bins步长规则:按照等差数列的方式步长拆分,前一章有详细讲解。以32位系统为例,前64个bin步长8,后32个bin步长64,后16个bin步长512,后8个步长4096,后四个32768,后2个262144。

我们看一下chunk的数据结构大小的图(64位系统):

三、mchunk_size的标记位的复用


mchunk_size字段的最后三个bit位,复用用作了(AMP)的标记位置。后三位bit位的复用,不会影响size的数据大小

  • A:A=0 为主分区分配,A=1 为非主分区分配,参见后面
  • M:M=1表示使用mmap映射区域,M=0为使用heap区域
  • P:P=1 表示pre_chunk空闲,mchunk_prev_size才有效

四、malloc_chunk的宏函数操作


malloc.c文件中定义了malloc_chunk的一系列相关宏的操作,重要定义如下:

  • chunk2mem:chunk的起始地址转换到用户内存mem地址。chunk起始地址在低地址,所以通过加上2*SIZE_SZ的方式,转换到高地址的mem地址指针
  • mem2chunk:用户内存mem地址转换到chunk的起始地址。用户内存mem地址在高地址,所以通过减去2*SIZE_SZ的方式,转到低地址的chunk的起始地址
  • MIN_CHUNK_SIZE:最小的chunk大小。通过offsetof 函数计算出 fd_nextsize 在 malloc_chunk 中的偏移,至少包含mchunk_prev_size、mchunk_size、fd、bk四个地址的空间量。所以64位系统,最小是32字节(4*8);32位系统,最小是16字节(4*4)
  • aligned_OK:检查内存是否对齐。64位系统按照16字节对齐,32位系统按照8字节对齐
  • request2size:通过对齐后,实际chunk的大小。如果内存大小小于MINSIZE,则使用MINSIZE空间;否则通过MALLOC_ALIGN_MASK进行字节对齐。
/* conversion from malloc headers to user pointers, and back */

#define chunk2mem(p)   ((void*)((char*)(p) + 2*SIZE_SZ))
#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))

/* The smallest possible chunk */
#define MIN_CHUNK_SIZE        (offsetof(struct malloc_chunk, fd_nextsize))

/* The smallest size we can malloc is an aligned minimal chunk */

#define MINSIZE  \
  (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))

/* Check if m has acceptable alignment */

#define aligned_OK(m)  (((unsigned long)(m) & MALLOC_ALIGN_MASK) == 0)

#define misaligned_chunk(p) \
  ((uintptr_t)(MALLOC_ALIGNMENT == 2 * SIZE_SZ ? (p) : chunk2mem (p)) \
   & MALLOC_ALIGN_MASK)

/* pad request bytes into a usable size -- internal version */

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

/* Check if REQ overflows when padded and aligned and if the resulting value
   is less than PTRDIFF_T.  Returns TRUE and the requested size or MINSIZE in
   case the value is less than MINSIZE on SZ or false if any of the previous
   check fail.  */
static inline bool
checked_request2size (size_t req, size_t *sz) __nonnull (1)
{
  if (__glibc_unlikely (req > PTRDIFF_MAX))
    return false;
  *sz = request2size (req);
  return true;
}


/* MALLOC_ALIGNMENT is the minimum alignment for malloc'ed chunks.  It
   must be a power of two at least 2 * SIZE_SZ, even on machines for
   which smaller alignments would suffice. It may be defined as larger
   than this though. Note however that code and data structures are
   optimized for the case of 8-byte alignment.  */
#define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \
			  ? __alignof__ (long double) : 2 * SIZE_SZ)

本文通过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(
<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、付费专栏及课程。

余额充值