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

5.3     核心结构体分析

每个分配区是 struct malloc_state 的一个实例, ptmalloc 使用 malloc_state 来管理分配区,而参数管理使用 struct malloc_par ,全局拥有一个唯一的 malloc_par 实例。

 

5.3.1  malloc_state

 

stuct  malloc_state 的定义如下:

struct malloc_state {
  /* Serialize access.  */
  mutex_t mutex;

  /* Flags (formerly in max_fast).  */
  int flags;

#if THREAD_STATS
  /* Statistics for locking.  Only used if THREAD_STATS is defined.  */
  long stat_lock_direct, stat_lock_loop, stat_lock_wait;
#endif

  /* Fastbins */
  mfastbinptr      fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr        top;

  /* The remainder from the most recent split of a small request */
  mchunkptr        last_remainder;

  /* Normal bins packed as described above */
  mchunkptr        bins[NBINS * 2 - 2];

  /* Bitmap of bins */
  unsigned int     binmap[BINMAPSIZE];

  /* Linked list */
  struct malloc_state *next;

#ifdef PER_THREAD
  /* Linked list for free arenas.  */
  struct malloc_state *next_free;
#endif

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

 Mutex 用于串行化访问分配区,当有多个线程访问同一个分配区时,第一个获得这个 mutex 的线程将使用该分配区分配内存,分配完成后,释放该分配区的 mutex ,以便其它线程使用该分配区。

Flags 记录了分配区的一些标志, bit0 用于标识分配区是否包含至少一个 fast bin chunkbit1 用于标识分配区是否能返回连续的虚拟地址空间。

/*
  FASTCHUNKS_BIT held in max_fast indicates that there are probably
  some fastbin chunks. It is set true on entering a chunk into any
  fastbin, and cleared only in malloc_consolidate.

  The truth value is inverted so that have_fastchunks will be true
  upon startup (since statics are zero-filled), simplifying
  initialization checks.
*/
#define FASTCHUNKS_BIT        (1U)
#define have_fastchunks(M)     (((M)->flags &  FASTCHUNKS_BIT) == 0)
#ifdef ATOMIC_FASTBINS
#define clear_fastchunks(M)    catomic_or (&(M)->flags, FASTCHUNKS_BIT)
#define set_fastchunks(M)      catomic_and (&(M)->flags, ~FASTCHUNKS_BIT)
#else
#define clear_fastchunks(M)    ((M)->flags |=  FASTCHUNKS_BIT)
#define set_fastchunks(M)      ((M)->flags &= ~FASTCHUNKS_BIT)
#endif

 上面的宏用于设置或是置位 flagsfast chunk 的标志位 bit0 ,如果 bit00 ,表示分配区中有 fast chunk ,如果为 1 表示没有 fast chunk ,初始化完成后的 malloc_state 实例中, flags 值为 0 ,表示该分配区中有 fast chunk ,但实际上没有,试图从 fast bins 中分配 chunk 都会返回 NULL ,在第一次调用函数 malloc_consolidate()fast bins 进行 chunk 合并时,如果 max_fast 大于 0 ,会调用 clear_fastchunks 宏, 标志该分配区中已经没 有 fast chunk ,因为函数 malloc_consolidate() 会合并所有的 fast bins 中的 chunkclear_fastchunks 宏只会在函数 malloc_consolidate() 中调用。当有 fast chunk 加入 fast bins 时,就是调用 set_fastchunks 宏标识分配区的 fast bins 中存在 fast chunk

/*
  NONCONTIGUOUS_BIT indicates that MORECORE does not return contiguous
  regions.  Otherwise, contiguity is exploited in merging together,
  when possible, results from consecutive MORECORE calls.

  The initial value comes from MORECORE_CONTIGUOUS, but is
  changed dynamically if mmap is ever used as an sbrk substitute.
*/
#define NONCONTIGUOUS_BIT     (2U)
#define contiguous(M)          (((M)->flags &  NONCONTIGUOUS_BIT) == 0)
#define noncontiguous(M)       (((M)->flags &  NONCONTIGUOUS_BIT) != 0)
#define set_noncontiguous(M)   ((M)->flags |=  NONCONTIGUOUS_BIT)
#define set_contiguous(M)      ((M)->flags &= ~NONCONTIGUOUS_BIT)

Flags 的 bit1 如果为 0 ,表示 MORCORE 返回连续虚拟地址空间, bit11 ,表示 MORCORE 返回非连续虚拟地址空间,对于主分配区, MORECORE 其实为 sbr() ,默认返回连续虚拟地址空间,对于非主分配区,使用 mmap() 分配大块虚拟内存,然后进行切分来模拟主分配区的行为,而默认情况下 mmap 映射区域是不保证虚拟地址空间连续的,所以非住分配区默认分配非连续虚拟地址空间。

 

Malloc_state 中声明了几个对锁的统计变量,默认没有定义 THREAD_STATS , 所以不会对锁的争用情况做统计。

fastbinsY 拥有 10NFASTBINS )个元素的数组,用于存放每个 fast chunk 链表头指针,所以 fast bins 最多包含 10fast chunk 的单向链表。

top 是一个 chunk 指针,指向分配区的 top chunk

last_remainder 是一个 chunk 指针,分配区上次分配 small chunk 时,从一个 chunk 中分裂出一个 small chunk 返回给用户,分裂后的剩余部分形成一个 chunklast_remainder 就是指向的这个 chunk

bins 用于存储 unstored binsmall binslarge binschunk 链表头, small bins 一共 62 个, large bins 一共 63 个,加起来一共 125bin 。而 NBINS 定义为 128 ,其实 bin[0]bin[127] 都不存在, bin[1]unsorted binchunk 链表头,所以实际只有 126binsBins 数组能存放了 254NBINS*2 – 2 )个 mchunkptr 指针,而我们实现需要存储 chunk 的实例,一般情况下, chunk 实例的大小为 6mchunkptr 大小,这 254 个指针的大小怎么能存下 126chunk 呢?这里使用了一个技巧,如果按照我们的常规想法,也许会申请 126malloc_chunk 结构体指针元素的数组,然后再给链表申请一个头节点(即 126 个),再让每个指针元素正确指向而形成 126 个具有头节点的链表。事实上,对于 malloc_chunk 类型的链表“头节点”,其内的 prev_sizesize 字段是没有任何实际作用的, fd_nextsizebk_nextsize 字段只有 large bins 中的空闲 chunk 才会用到,而对于 large bins 的空闲 chunk 链表头不需要这两个字段,因此这四个字段所占空间如果不合理使用的话那就是白白的浪费。我们再来看一看 128malloc_chunk 结构体指针元素的数组占了多少内存空间呢?假设 SIZE_SZ 的大小为 8B ,则指针的大小也为 8B ,结果为 126*2*8=2016 字节。而 126malloc_chunk 类型的链表“头节点”需要多少内存呢? 126*6*8=6048 ,真的是 6048B 么?不是,刚才不是说了, prev_sizesizefd_nextsizebk_nextsize 这四个字段是没有任何实际作用的,因此完全可以被重用(覆盖),因此实际需要内存为 126*2*8=2016Bins 指针数组的大小为,( 128*2-2*8=2032,2032 大于 2016 (事实上最后 16 个字节都被浪费掉了),那么这 254malloc_chunk 结构体指针元素数组所占内存空间就可以存储这 126 个头节点了。

binmap 字段是一个 int 数组, ptmalloc 用一个 bit 来标识该 bit 对应的 bin 中是否包含空闲 chunk

/*
  Binmap

    To help compensate for the large number of bins, a one-level index
    structure is used for bin-by-bin searching.  `binmap' is a
    bitvector recording whether bins are definitely empty so they can
    be skipped over during during traversals.  The bits are NOT always
    cleared as soon as bins are empty, but instead only
    when they are noticed to be empty during traversal in malloc.
*/

/* Conservatively use 32 bits per map word, even if on 64bit system */
#define BINMAPSHIFT      5
#define BITSPERMAP       (1U << BINMAPSHIFT)
#define BINMAPSIZE       (NBINS / BITSPERMAP)

#define idx2block(i)     ((i) >> BINMAPSHIFT)
#define idx2bit(i)       ((1U << ((i) & ((1U << BINMAPSHIFT)-1))))

#define mark_bin(m,i)    ((m)->binmap[idx2block(i)] |=  idx2bit(i))
#define unmark_bin(m,i)  ((m)->binmap[idx2block(i)] &= ~(idx2bit(i)))
#define get_binmap(m,i)  ((m)->binmap[idx2block(i)] &   idx2bit(i))

 binmap一共 128bit16 字节, 4int 大小, binmapint 分成 4block ,每个 block32bit ,根据 bin indx 可以使用宏 idx2block 计算出该 binbinmap 对应的 bit 属于哪个 blockidx2bit 宏取第 i 位为 1 ,其它位都为 0 的掩码,举个例子: idx2bit(3) 为 “ 0000 1000 ”(只显示 8 位)。 mark_bin 设置第 ibinbinmap 中对应的 bit 位为 1unmark_bin 设置第 ibinbinmap 中对应的 bit 位为 0get_binmap 获取第 ibinbinmap 中对应的 bit

next 字段用于将分配区以单向链表链接起来。

next_free 字段空闲的分配区链接在单向链表中,只有在定义了 PER_THREAD 的情况下才定义该字段。

system_mem 字段记录了当前分配区已经分配的内存大小。

max_system_mem 记录了当前分配区最大能分配的内存大小。

5.3.2  Malloc_par

 

Struct malloc_par 的定义如下:

struct malloc_par {
  /* Tunable parameters */
  unsigned long    trim_threshold;
  INTERNAL_SIZE_T  top_pad;
  INTERNAL_SIZE_T  mmap_threshold;
#ifdef PER_THREAD
  INTERNAL_SIZE_T  arena_test;
  INTERNAL_SIZE_T  arena_max;
#endif

  /* Memory map support */
  int              n_mmaps;
  int              n_mmaps_max;
  int              max_n_mmaps;
  /* the mmap_threshold is dynamic, until the user sets
     it manually, at which point we need to disable any
     dynamic behavior. */
  int              no_dyn_threshold;

  /* Cache malloc_getpagesize */
  unsigned int     pagesize;

  /* Statistics */
  INTERNAL_SIZE_T  mmapped_mem;
  INTERNAL_SIZE_T  max_mmapped_mem;
  INTERNAL_SIZE_T  max_total_mem; /* only kept for NO_THREADS */

  /* First address handed out by MORECORE/sbrk.  */
  char*            sbrk_base;
};

trim_threshold 字段表示收缩阈值,默认为 128KB ,当每个分配区的 top chunk 大小大于这个阈值时,在一定的条件下,调用 free 时会收缩内存,减小 top chunk 的大小。由于 mmap 分配阈值的动态调整,在 free 时可能将收缩阈值修改为 mmap 分配阈值的 2 倍,在 64 位系统上, mmap 分配阈值最大值为 32MB ,所以收缩阈值的最大值为 64MB ,在 32 位系统上, mmap 分配阈值最大值为 512KB ,所以收缩阈值的最大值为 1MB 。收缩阈值可以通过函数 mallopt() 进行设置。

top_pad 字段表示在分配内存时是否添加额外的 pad ,默认该字段为 0

mmap_threshold 字段表示 mmap 分配阈值,默认值为 128KB ,在 32 位系统上最大值为 512KB64 位系统上的最大值为 32MB ,由于默认开启 mmap 分配阈值动态调整,该字段的值会动态修改,但不会超过最大值。

arena_testarena_max 用于 PER_THREAD 优化,在 32 位系统上 arena_test 默认值为 264 位系统上的默认值为 8 ,当每个进程的分配区数量小于等于 arena_test 时,不会重用已有的分配区。为了限制分配区的总数,用 arena_max 来保存分配区的最大数量,当系统中的分配区数量达到 arena_max ,就不会再创建新的分配区,只会重用已有的分配区。这两个字段都可以使用 mallopt() 函数设置。

n_mmaps 字段表示当前进程使用 mmap() 函数分配的内存块的个数。

n_mmaps_max 字段表示进程使用 mmap() 函数分配的内存块的最大数量,默认值为 65536 ,可以使用 mallopt() 函数修改。

max_n_mmaps 字段表示当前进程使用 mmap() 函数分配的内存块的数量的最大值,有关系 n_mmaps <= max_n_mmaps 成立。这个字段是由于 mstats() 函数输出统计需要这个字段。

no_dyn_threshold 字段表示是否开启 mmap 分配阈值动态调整机制,默认值为 0 ,也就是默认开启 mmap 分配阈值动态调整机制。

pagesize 字段表示系统的页大小,默认为 4KB

mmapped_memmax_mmapped_mem 都用于统计 mmap 分配的内存大小,一般情况下两个字段的值相等, max_mmapped_mem 用于 mstats() 函数。

max_total_mem 字段在单线程情况下用于统计进程分配的内存总数。

sbrk_base 字段表示堆的起始地址。

5.3.3 分配区的初始化

 

Ptmalloc 定义了如下几个全局变量:

/* There are several instances of this struct ("arenas") in this
   malloc.  If you are adapting this malloc in a way that does NOT use
   a static or mmapped malloc_state, you MUST explicitly zero-fill it
   before using. This malloc relies on the property that malloc_state
   is initialized to all zeroes (as is true of C statics).  */
static struct malloc_state main_arena;
/* There is only one instance of the malloc parameters.  */
static struct malloc_par mp_;
/* Maximum size of memory handled in fastbins.  */
static INTERNAL_SIZE_T global_max_fast;

main_arena 表示主分配区,任何进程有且仅有一个全局的主分配区, mp_ 是全局唯一的一个 malloc_par 实例,用于管理参数和统计信息, global_max_fast 全局变量表示 fast bins 中最大的 chunk 大小。

 

分配区 main_arena 初始化函数

/*
  Initialize a malloc_state struct.

  This is called only from within malloc_consolidate, which needs
  be called in the same contexts anyway.  It is never called directly
  outside of malloc_consolidate because some optimizing compilers try
  to inline it at all call points, which turns out not to be an
  optimization at all. (Inlining it in malloc_consolidate is fine though.)
*/
#if __STD_C
static void malloc_init_state(mstate av)
#else
static void malloc_init_state(av) mstate av;
#endif
{
  int     i;
  mbinptr bin;

  /* 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)
    set_max_fast(DEFAULT_MXFAST);
  av->flags |= FASTCHUNKS_BIT;

  av->top            = initial_top(av);
}

 分配区的初始化函数默认分配区的实例 av 是全局静态变量或是已经将 av 中的所有字段都清 0 了。初始化函数做的工作比较简单,首先遍历所有的 bins ,初始化每个 bin 的空闲链表为空,即将 binfbbk 都指向 bin 本身。由于 av 中所有字段默认为 0 ,即默认分配连续的虚拟地址空间,但只有主分配区才能分配连续的虚拟地址空间,所以对于非主分配区,需要设置为分配非连续虚拟地址空间。如果初始化的是主分配区,需要设置 fast bins 中最大 chunk 大小,由于主分配区只有一个,并且一定是最先初始化,这就保证了对全局变量 global_max_fast 只初始化了一次,只要该全局变量的值非 0 ,也就意味着主分配区初始化了。最后初始化 top chunk

 

Ptmalloc 参数初始化

/* Set up basic state so that _int_malloc et al can work.  */
static void
ptmalloc_init_minimal (void)
{
#if DEFAULT_TOP_PAD != 0
  mp_.top_pad        = DEFAULT_TOP_PAD;
#endif
  mp_.n_mmaps_max    = DEFAULT_MMAP_MAX;
  mp_.mmap_threshold = DEFAULT_MMAP_THRESHOLD;
  mp_.trim_threshold = DEFAULT_TRIM_THRESHOLD;
  mp_.pagesize       = malloc_getpagesize;
#ifdef PER_THREAD
# define NARENAS_FROM_NCORES(n) ((n) * (sizeof(long) == 4 ? 2 : 8))
  mp_.arena_test     = NARENAS_FROM_NCORES (1);
  narenas = 1;
#endif
}

 主要是将全局变量 mp_ 的字段初始化为默认值,值得一提的是,如果定义了编译选项 PER_THREAD ,会根据系统 cpu 的个数设置 arena_test 的值,默认 32 位系统是双核, 64 位系统为 8 核, arena_test 也就设置为相应的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: glibc是GNU计划的一部分,是一套C语言标准库。内存管理是其中的一个重要组件。而ptmalloc2是glibc内存管理的一种算法,用于分配和释放内存块。 要下载glibc内存管理ptmalloc2源代码,可以通过以下几个步骤进行: 1. 打开GNU官方网站,找到glibc的相关页面,通常在https://www.gnu.org/software/libc/ 。 2. 在该页面上,找到下载链接或源代码仓库地址,这个地址通常会提供给用户下载最新版本的glibc。 3. 点击下载链接或者复制源代码仓库地址,将其粘贴到浏览器地址栏中。 4. 打开该链接后,您将能够下载一个压缩文件(通常是tar.gz或tar.bz2格式),包含了glibc的全部源代码。 5. 下载完毕后,解压压缩文件。您可以使用解压软件,如WinRAR或7-Zip。解压缩后,您将获得一个包含许多目录和文件的文件夹。 6. 在解压后的文件夹中,找到与ptmalloc2相关的源代码文件。通常这些文件会位于glibc源代码malloc目录下。 7. 在malloc目录中,您将能够找到ptmalloc2源代码文件,这些文件名通常以"ptmalloc"或"ptmalloc2"开头。 以上是下载glibc内存管理ptmalloc2源代码的一个大致过程。通过该源代码,您可以深入了解ptmalloc2算法是如何在glibc中实现内存分配和释放的。但是请注意,阅读和理解源代码需要一定的计算机编程经验和相关背景知识。 ### 回答2: glibc是Linux操作系统中非常重要的一个C标准库,ptmalloc2是glibc中负责内存管理的模块之一。该模块负责动态分配和释放内存,并提供了多种内存分配器算法。 ptmalloc2源代码分析是深入研究该模块源代码的过程。通过分析ptmalloc2源代码,可以了解到它的实现原理、内存分配算法以及性能优化等方面的细节。 在下载ptmalloc2源代码之后,我们可以通过阅读和分析源代码来了解其内部结构和工作原理。在源代码中,我们可以找到一些关键的数据结构和函数,如malloc、free、realloc等。这些函数实现了动态内存分配和释放的基本功能。 通过阅读源代码,我们可以学习到ptmalloc2内存管理器的特点和优势。例如,ptmalloc2采用了分离的空闲链表来管理不同大小的内存块,利用了空闲块合并和分割等技术来提高内存的利用率和性能。此外,源代码还可能包含一些与内存操作相关的底层函数和宏定义。 分析ptmalloc2源代码不仅可以帮助我们理解其内部实现,还可以为我们定位和解决内存管理相关的问题提供指导。如果遇到性能问题或者内存泄漏等现象,我们可以通过分析源代码来找到问题的根源,并提出相应的优化措施。 总之,通过对glibc内存管理模块ptmalloc2的源代码进行分析,我们可以深入了解其实现原理和内部机制,为我们在实际项目中正确、高效地使用内存管理功能提供帮助。 ### 回答3: glibc是Linux系统上使用最广泛的C语言函数库,而ptmalloc2则是glibc中负责内存分配和管理的部分源代码。 首先,需要明确的是,glibcptmalloc2源代码并不是一个独立的项目,而是glibc库中的一部分。如果需要下载该源代码,可以通过访问glibc的官方网站或者使用git等工具来获取。 分析glibc内存管理ptmalloc2源代码可以帮助开发者更好地理解和使用glibc的内存分配功能。ptmalloc2实现了一种基于堆的内存分配算法,它采用了多种策略来管理内存,如bin和fastbin等。源代码分析可以帮助我们了解这些策略的具体实现细节,以及它们在不同场景下的行为。 要对ptmalloc2源代码进行分析,可以首先阅读相关文档,如glibc的官方文档或论文。 掌握ptmalloc2的整体架构、数据结构和算法等基本知识后,可以通过逐行或逐函数地阅读源代码来深入理解其内部工作机制。可以关注一些关键函数的实现,如malloc、free、realloc等,以及相关的数据结构和算法。 此外,还可以参考开源社区中对ptmalloc2源代码分析和解读,如一些博客文章、论文或代码注释等。这些资源通常提供了对源代码更深入的解释和讨论,对于理解ptmalloc2的实现细节会有所帮助。 总之,通过下载并分析glibc内存管理ptmalloc2源代码,可以帮助我们更好地理解和使用glibc库中的内存分配功能。同时,也可以通过分析源代码来提高我们的代码调试和性能优化能力,并为开发更高效的内存管理算法提供参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值