malloc源码分析——ptmalloc

1. malloc源码分析—ptmalloc

1.1 malloc_hook_ini

本文分析malloc的源码,首先从glibc开始,首先看malloc.c文件中的一段定义,

1
strong_alias (__libc_malloc, __malloc) strong_alias (__libc_malloc, malloc)

strong_alias是GNU C中的定义,编译器判定这里malloc是__libc_malloc的别名,__libc_malloc定义在malloc.c中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void * __libc_malloc (size_t bytes){
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *) = atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
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);
}
if (ar_ptr != NULL)
(void) mutex_unlock (&ar_ptr->mutex);
return victim;
}
libc_hidden_def (__libc_malloc)

首先看atomic_forced_read

1
2
# define atomic_forced_read(x) \
({ __typeof (x) __x; __asm ("" : "=r" (__x) : "0" (x)); __x; })

__typeof是原始函数的返回类型,后面是一段汇编代码,”0”是零,即%0,引用时不可以加 %,只能input引用output,这里就是原子读,将__malloc_hook的地址放入任意寄存器(r)再取出。__malloc_hook的定义如下

1
void *weak_variable (*__malloc_hook)(size_t __size, const void *) = malloc_hook_ini;

weak_variable其实就是,

1
__attribute__ ((weak))

和编译器有关,这里不管它。__builtin_expect其实就是告诉编译器if判断语句里大多数情况下的值,这样编译器可以做优化,避免过多的跳转。回到__libc_malloc接下来就是调用malloc_hook_ini进行内存的分配。
malloc_hook_ini定义在hooks.c中,

1
2
3
4
5
static void * malloc_hook_ini (size_t sz, const void *caller){
__malloc_hook = NULL;
ptmalloc_init ();
return __libc_malloc (sz);
}

1.2 ptmalloc_init

ptmalloc_init用来对整个ptmalloc框架进行初始化,定义在arena.c中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
static void ptmalloc_init(void) {
if (__malloc_initialized >= 0)
return;
__malloc_initialized = 0;
tsd_key_create(&arena_key, NULL);
tsd_setspecific(arena_key, (void *) &main_arena);
thread_atfork(ptmalloc_lock_all, ptmalloc_unlock_all, ptmalloc_unlock_all2);
const char *s = NULL;
if (__glibc_likely(_environ != NULL)) {
char **runp = _environ;
char *envline;
while (__builtin_expect((envline = next_env_entry(&runp)) != NULL, 0)) {
size_t len = strcspn(envline, "=");
if (envline[len] != '=')
continue;
switch (len) {
case 6:
if (memcmp(envline, "CHECK_", 6) == 0)
s = &envline[7];
break;
case 8:
if (!__builtin_expect(__libc_enable_secure, 0)) {
if (memcmp(envline, "TOP_PAD_", 8) == 0)
__libc_mallopt(M_TOP_PAD, atoi(&envline[9]));
else if (memcmp(envline, "PERTURB_", 8) == 0)
__libc_mallopt(M_PERTURB, atoi(&envline[9]));
}
break;
case 9:
if (!__builtin_expect(__libc_enable_secure, 0)) {
if (memcmp(envline, "MMAP_MAX_", 9) == 0)
__libc_mallopt(M_MMAP_MAX, atoi(&envline[10]));
else if (memcmp(envline, "ARENA_MAX", 9) == 0)
__libc_mallopt(M_ARENA_MAX, atoi(&envline[10]));
}
break;
case 10:
if (!__builtin_expect(__libc_enable_secure, 0)) {
if (memcmp(envline, "ARENA_TEST", 10) == 0)
__libc_mallopt(M_ARENA_TEST, atoi(&envline[11]));
}
break;
case 15:
if (!__builtin_expect(__libc_enable_secure, 0)) {
if (memcmp(envline, "TRIM_THRESHOLD_", 15) == 0)
__libc_mallopt(M_TRIM_THRESHOLD, atoi(&envline[16]));
else if (memcmp(envline, "MMAP_THRESHOLD_", 15) == 0)
__libc_mallopt(M_MMAP_THRESHOLD, atoi(&envline[16]));
}
break;
default:
break;
}
}
}
if (s && s[0]) {
__libc_mallopt(M_CHECK_ACTION, (int) (s[0] - '0'));
if (check_action != 0)
__malloc_check_init();
}
void (*hook)(void) = atomic_forced_read (__malloc_initialize_hook);
if (hook != NULL)
(*hook)();
__malloc_initialized = 1;
}

首先检查全局变量__malloc_initialized是否大于等于0,如果该值大于0,表示ptmalloc已经初始化,如果改值为0,表示ptmalloc正在初始化,全局变量__malloc_initialized用来保证全局只初始化ptmalloc一次。
tsd_key_create创建线程私有实例arena_key,该私有实例保存的是分配区(arena)的malloc_state实例指针。arena_key指向的可能是主分配区的指针,也可能是非主分配区的指针,这里将调用ptmalloc_init()的线程的arena_key绑定到主分配区上。意味着本线程首选从主分配区分配内存。arena_key在glibc中是一个线程私有变量,

1
2
3
#define tsd_key_create(key, destr) ((void) (key))
#define tsd_setspecific(key, data) __libc_tsd_set (void *, MALLOC, (data))
#define __libc_tsd_set(TYPE, KEY, VALUE) (__libc_tsd_##KEY = (VALUE))

tsd_setspecific(arena_key, (void *) &main_arena);就是__libc_tsd_MALLOC = &main_arena
thread_atfork用来设置进程在fork创建子进程时关于锁设置的各个函数,ptmalloc_lock_allptmalloc_unlock_all用来给父进程加锁解锁,ptmalloc_unlock_all2用来给子进程调用以解锁。

1
2
3
4
5
6
7
# define thread_atfork(prepare, parent, child) \
atfork_mem.prepare_handler = prepare; \
atfork_mem.parent_handler = parent; \
atfork_mem.child_handler = child; \
atfork_mem.dso_handle = &__dso_handle == NULL ? NULL : __dso_handle; \
atfork_mem.refcntr = 1; \
__linkin_atfork (&atfork_mem)

其中,atfork_mem是一个全局的fork时的函数子针结构体fork_handler,

1
#define ATFORK_MEM static struct fork_handler atfork_mem1

__linkin_atfork用于将刚刚构造的fork_handler添加进全局链表__fork_handlers中而不用加锁,其实就是一个CAS锁,关于该锁,可以查阅网上资料,

1
2
3
4
5
6
void attribute_hidden __linkin_atfork(struct fork_handler *newp) {
do
newp->next = __fork_handlers;
while (catomic_compare_and_exchange_bool_acq(&__fork_handlers, newp,
newp->next) != 0);
}

catomic_compare_and_exchange_bool_acq最后是一个宏定义,将之改写后如下

1
2
3
4
5
6
7
8
9
10
11
{
fork_handler* __atg4_old = newp->next;
long __gmemp = &__fork_handlers;
ATOMIC();
fork_handler* __gret = *__gmemp;
fork_handler* __gnewval = newp;
if (__gret == __atg4_old)
*__gmemp = newp;
ENDATOMIC();
__gret;
}

gcc会将这段代码进行编译,生成的代码无法被中断。因此简单说来,__linkin_atfork就是将fork_handler原子添加进全局链表__fork_handlers中。

回到ptmalloc_init函数中,接下来就是进行环境变量的设置,__glibc_likely和gcc的编译优化相关,不管他。_environ就是__environ,里面保存了环境变量,下面就是根据各个环境变量调用__libc_mallopt进行设置,后面来看这个函数。

ptmalloc_init然后获取__malloc_initialize_hook函数指针并执行,由于该函数和malloc没有直接关系,这里不管它。最后将__malloc_initialized设置为1,表是初始化完成。

1.3 __libc_mallopt

__libc_mallopt定义在malloc.c中,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
int __libc_mallopt(int param_number, int value) {
mstate av = &main_arena;
int res = 1;
if (__malloc_initialized < 0)
ptmalloc_init();
(void) mutex_lock(&av->mutex);
malloc_consolidate(av);
LIBC_PROBE (memory_mallopt, 2, param_number, value);
switch (param_number) {
case M_MXFAST:
if (value >= 0 && value <= MAX_FAST_SIZE) {
LIBC_PROBE (memory_mallopt_mxfast, 2, value, get_max_fast ());
set_max_fast(value);
} else
res = 0;
break;
case M_TRIM_THRESHOLD:
LIBC_PROBE (memory_mallopt_trim_threshold, 3, value,
mp_.trim_threshold, mp_.no_dyn_threshold);
mp_.trim_threshold = value;
mp_.no_dyn_threshold = 1;
break;
case M_TOP_PAD:
LIBC_PROBE (memory_mallopt_top_pad, 3, value,
mp_.top_pad, mp_.no_dyn_threshold);
mp_.top_pad = value;
mp_.no_dyn_threshold = 1;
break;
case M_MMAP_THRESHOLD:
if ((unsigned long) value > HEAP_MAX_SIZE / 2)
res = 0;
else {
LIBC_PROBE (memory_mallopt_mmap_threshold, 3, value,
mp_.mmap_threshold, mp_.no_dyn_threshold);
mp_.mmap_threshold = value;
mp_.no_dyn_threshold = 1;
}
break;
case M_MMAP_MAX:
LIBC_PROBE (memory_mallopt_mmap_max, 3, value,
mp_.n_mmaps_max, mp_.no_dyn_threshold);
mp_.n_mmaps_max = value;
mp_.no_dyn_threshold = 1;
break;
case M_CHECK_ACTION:
LIBC_PROBE (memory_mallopt_check_action, 2, value, check_action);
check_action = value;
break;
case M_PERTURB:
LIBC_PROBE (memory_mallopt_perturb, 2, value, perturb_byte);
perturb_byte = value;
break;
case M_ARENA_TEST:
if (value > 0) {
LIBC_PROBE (memory_mallopt_arena_test, 2, value, mp_.arena_test);
mp_.arena_test = value;
}
break;
case M_ARENA_MAX:
if (value > 0) {
LIBC_PROBE (memory_mallopt_arena_max, 2, value, mp_.arena_max);
mp_.arena_max = value;
}
break;
}
(void) mutex_unlock(&av->mutex);
return res;
}
libc_hidden_def( __libc_mallopt)

首先通过__malloc_initialized判断如果ptmalloc还未初始化,就调用ptmalloc_init进行初始化。malloc_consolidate用来将fast bins中的chunk合并,并且里面会初始化主分配区,后面的章节会分析到这个函数。然后就根据传入的param_number设置mp_mp_代表ptmalloc的各个全局参数,其默认定义如下

1
2
3
4
5
6
7
8
static struct malloc_par mp_ = {
.top_pad = DEFAULT_TOP_PAD,
.n_mmaps_max = DEFAULT_MMAP_MAX,
.mmap_threshold = DEFAULT_MMAP_THRESHOLD,
.trim_threshold = DEFAULT_TRIM_THRESHOLD,
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
.arena_test = NARENAS_FROM_NCORES(1)
};

这里不分析里面各个参数的意义,到后面用到时再来分析。malloc_hook_ini最后会回调__libc_malloc函数,这次__malloc_hook为null,因此继续看下面的代码。

1.4 arena_get

接下来通过arena_get获得一个分配区,arena_get是个宏定义,定义在arena.c中,

1
2
3
4
#define arena_get(ptr, size) do { \
arena_lookup (ptr); \
arena_lock (ptr, size); \
} while (0)

arena_lookup从私有变量里获取分配区指针,

1
2
3
4
#define arena_lookup(ptr) do { \
void *vptr = NULL; \
ptr = (mstate) tsd_getspecific (arena_key, vptr); \
} while (0)

tsd_getspecific也是个宏定义,就是获取前面调用tsd_setspecific设置的分配区指针,这里取出的可能是主分配去指针,也可能是非主分配去指针,然后调用arena_lockmalloc_state中的mutex加锁。

1
2
3
4
5
6
#define arena_lock(ptr, size) do { \
if (ptr && !arena_is_corrupt (ptr)) \
(void) mutex_lock (&ptr->mutex); \
else \
ptr = arena_get2 (ptr, (size), NULL); \
} while (0)

获得分配去的指针后,就会调用_int_malloc开始分配内存了,下一章分析这个函数。