ptmalloc源码分析 - ptmalloc的初始化实现(01)

本文介绍了Linux下的内存管理器ptmalloc,包括其发展历程、工作原理及如何处理多线程下的内存分配和回收。文中详细分析了ptmalloc的初始化过程,以及入口函数__libc_malloc的工作流程。

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

目录

一、ptmalloc的简介

二、入口函数__libc_malloc

1. 初始化的原子模式atomic_forced_read

2. 初始化的调用顺序malloc_hook_ini

3. 初始化的核心逻辑ptmalloc_init 


前几年阅读过华庭的《glibc内存管理ptmalloc源代码分析》文章,并做过一篇笔记:《Linux c 开发 - 内存管理器ptmalloc》

今年打算重点阅读一下glibc里面,ptmalloc部分的具体实现机制。

一、ptmalloc的简介


Linux早期的版本,是由Doug Lea实现的,但是早期的版本有一个问题,就是没办法处理多线程下并发分配和回收的高效和正确性。Wolfram Gloger在Doug Lea的基础上改进使得Glibc的malloc可以支持多线程,ptmalloc。在glibc-2.3.x.中已经集成了ptmalloc2,所以我们平时使用Linux系统的时候,使用到的内存分配器就是ptmalloc。

ptmalloc实现了malloc(),free()以及一组其它的函数,提供系统级别的内存管理。

ptmalloc内存分配器处于用户程序和内核之间,通过malloc/free等函数响应上层使用者的内存分配请求。ptmalloc向操作系统申请内存,然后返回到上层用户层。

为了保持高效的分配,分配器一般都会预先分配一块大于用户请求的内存,并通过某种算法管理这块内存。来满足用户的内存分配要求,用户释放掉的内存也并不是立即就返回给操作系统,相反,分配器会管理这些被释放掉的空闲空间,以应对用户以后的内存分配要求。也就是说,分配器不但要管理已分配的内存块,还需要管理空闲的内存块,当响应用户分配要求时,分配器会首先在空闲空间中寻找一块合适的内存给用户,在空闲空间中找不到的情况下才分配一块新的内存。

独立的进程之间,都会有独立的分配器,每个进程相当于独享了一块内存空间。但是虚拟内存地址和实际内存也有映射关系,每个进程可使用的内存空间也是受限的。

通过下载glic2.31源码包,我们就能看到ptmalloc的源码实现。

二、入口启动函数__libc_malloc


ptmalloc的源码在glibc/malloc文件夹下。malloc函数的入口在malloc/malloc.c文件中。

但是我们没法直接找到malloc的函数,这里glibc就是通过别名机制string_alias,从malloc映射到__libc_malloc函数。

所以,当用户调用malloc(xx)分配内存的时候,实际调用了malloc.c文件中的__libc_malloc函数。


strong_alias (__libc_calloc, __calloc) weak_alias (__libc_calloc, calloc)
strong_alias (__libc_free, __free) strong_alias (__libc_free, free)
strong_alias (__libc_malloc, __malloc) strong_alias (__libc_malloc, malloc)
strong_alias (__libc_memalign, __memalign)
weak_alias (__libc_memalign, memalign)
strong_alias (__libc_realloc, __realloc) strong_alias (__libc_realloc, realloc)
strong_alias (__libc_valloc, __valloc) weak_alias (__libc_valloc, valloc)
strong_alias (__libc_pvalloc, __pvalloc) weak_alias (__libc_pvalloc, pvalloc)
strong_alias (__libc_mallinfo, __mallinfo)
weak_alias (__libc_mallinfo, mallinfo)
strong_alias (__libc_mallopt, __mallopt) weak_alias (__libc_mallopt, mallopt)

weak_alias (__malloc_stats, malloc_stats)
weak_alias (__malloc_usable_size, malloc_usable_size)
weak_alias (__malloc_trim, malloc_trim)

1. 初始化的原子模式atomic_forced_read


我们先看一下入口函数__libc_malloc的核心代码

/**
 * malloc() 主函数入口
 */
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");

  /**
   * 1. __malloc_hook 值是 malloc_hook_ini ,初始化ptmalloc
   * 2. atomic_forced_read 汇编语句,原子读操作,读取__malloc_hook
   * 3. weak_variable 相当于属性赋值操作attribute,将malloc_hook_ini转到__malloc_hook
   * 4. malloc_hook_ini 中调用的是ptmalloc_init()函数,初始化ptmalloc
   */
  void *(*hook) (size_t, const void *)
    = atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(bytes, RETURN_ADDRESS (0));
  • mstate ar_ptr指向全局内存分配器的指针,说白了就是全局内存分配器状态机。具体的数据结构第二章详细讲解。
  • atomic_forced_read 是汇编语句,用于原子读操作,每次只会读取一次。例如调用malloc_hook_ini初始化只会调用一次
  • __malloc_hook指向malloc_hook_ini,该函数为ptmalloc的初始化函数。主要用于初始化全局状态机+chunk的数据结构
//---------原子读操作
#ifndef atomic_forced_read
# define atomic_forced_read(x) \
  ({ __typeof (x) __x; __asm ("" : "=r" (__x) : "0" (x)); __x; })
#endif

//__malloc_hook指向malloc_hook_ini函数
void *weak_variable (*__malloc_hook)
  (size_t __size, const void *) = malloc_hook_ini; //设置__malloc_hook函数

2. 初始化的调用顺序malloc_hook_ini


先看一下malloc_hook_ini函数:

/**
 * 初始化
 */
static void *
malloc_hook_ini (size_t sz, const void *caller)
{
  __malloc_hook = NULL;
  ptmalloc_init ();
  return __libc_malloc (sz);
}

先将__malloc_hook的值设置为NULL,然后调用ptmalloc_init函数,最后竟然又回调了__libc_malloc函数

在我们第一次调用 malloc 申请堆空间的时候,首先会进入 malloc_hook_ini 函数里面进行对 ptmalloc 的初始化工作,然后再次进入 __libc_malloc 的时候,此时钩子 __malloc_hook 已经被置空了,从而继续执行剩余的代码,即转入 _int_malloc 函数

可以看一下整体的调用逻辑和顺序:

3. 初始化的核心逻辑ptmalloc_init 


ptmalloc初始化过程核心就是初始化:全局内存分配器的状态机

  • 通过__malloc_initialized全局变量,来记录初始化的状态。0=未初始化,1-初始化。如果已经初始化,则直接返回
  • main_arena是全局内存分配器状态机的主线程结构,数据结构:mstate
  • malloc_init_state是核心初始化mstate状态机数据结构
/*
 * ptmalloc_init 初始化过程
 */
static void
ptmalloc_init (void)
{
	/**
	 * 1. 判断是否已经初始化,如果初始化过了,则不再执行;
	 * 2. 如果等于0,则正在初始化,如果等于1,则初始化完成
	 */
  if (__malloc_initialized >= 0)
    return;

  __malloc_initialized = 0;

#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

  /**
   * 1. main_arena为主分配区域
   * 2. malloc_init_state 初始化主分配区数据
   */
  thread_arena = &main_arena;

  malloc_init_state (&main_arena);
......
......
......
//设置hook值
#if HAVE_MALLOC_INIT_HOOK
  void (*hook) (void) = atomic_forced_read (__malloc_initialize_hook);
  if (hook != NULL)
    (*hook)();
#endif
  /* 初始化完毕,则设置为1 */
  __malloc_initialized = 1;
}

下一章,我们核心讲讲malloc_init_state初始化逻辑和状态机的数据结构。

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

余额充值