目录
前几年阅读过华庭的《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初始化逻辑和状态机的数据结构。