libmalloc "malloc" 探究

  1. iOS “malloc” 内存分配
    1. libmalloc 分析
    2. default_zone
    3. “malloc”
    4. malloc_logger
  2. 总结

之前腾讯开源了 OOMDetector 用于监控内存分配,在集成到我司项目时, 修复了一些bug崩溃,优化了性能,在内存不是频繁分配的App上是有用武之地的。

OOMDetector 监控内存分配的核心处理 是通过 libmalloc库 中的 malloc_logger 指针实现的。

我之前一直疑惑 OOMDetector 的监控方式是否能够完善的监控到应用层面的"malloc"内存分配。这就有必要探究下应用层的内存分配API

iOS “malloc” 内存分配

iOS上都通过kernel进行内存分配,将虚拟内存页映射到应用内存空间上。我们在应用层可以通过mmap实现这种内存分配。

不过大多数情况下我们都通过“malloc”进行内存分配, 我们可以使用malloc来获取内存,而不用每次都请求vmpage映射。而malloc分配的内存实质上都是从vmpage映射获取的。

你一定注意到了 “malloc”, 是的,它代表calloc, realloc, valloc, malloc_zone_malloc, malloc_zone_calloc, malloc_zone_valloc, malloc_zone_realloc, malloc_zone_batch_malloc 等方法,本质上他们的分配都应该被归于一类,都是利用 scalable_zone 进行分配的。

顺便提一下,C++ new的分配 其实现也是用 libc 中的 malloc 进行分配的
new_opnt.cc

libmalloc 分析

default_zone

本着质疑的精神,一般我是不会相信空口无凭的文章的,因此,可以从libmalloc中找到答案。

首先找到 malloc, calloc等函数.. 的实现,因为原理类似,就不一一举出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void *
malloc(size_t size)
{
void *retval;
retval = malloc_zone_malloc(default_zone, size);
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}
void *
calloc(size_t num_items, size_t size)
{
void *retval;
retval = malloc_zone_calloc(default_zone, num_items, size);
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}

首先来看看这个 default_zone 是什么东西, 代码如下

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
typedef struct {
malloc_zone_t malloc_zone;
uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)];
} virtual_default_zone_t;
static virtual_default_zone_t virtual_default_zone
__attribute__((section("__DATA,__v_zone")))
__attribute__((aligned(PAGE_MAX_SIZE))) = {
NULL,
NULL,
default_zone_size,
default_zone_malloc,
default_zone_calloc,
default_zone_valloc,
default_zone_free,
default_zone_realloc,
default_zone_destroy,
DEFAULT_MALLOC_ZONE_STRING,
default_zone_batch_malloc,
default_zone_batch_free,
&default_zone_introspect,
9,
default_zone_memalign,
default_zone_free_definite_size,
default_zone_pressure_relief
};
static malloc_zone_t *default_zone = &virtual_default_zone.malloc_zone;
static void *
default_zone_malloc(malloc_zone_t *zone, size_t size)
{
zone = runtime_default_zone();
return zone->malloc(zone, size);
}
MALLOC_ALWAYS_INLINE
static inline malloc_zone_t *
runtime_default_zone() {
return (lite_zone) ? lite_zone : inline_malloc_default_zone();
}

可以看到 default_zone 通过这种方式来初始化

1
2
3
4
5
6
7
static inline malloc_zone_t *
inline_malloc_default_zone(void)
{
_malloc_initialize_once();
// _malloc_printf(ASL_LEVEL_INFO, "In inline_malloc_default_zone with %d %d\n", malloc_num_zones, malloc_has_debug_zone);
return malloc_zones[0];
}

随后的调用如下
_malloc_initialize -> create_scalable_zone -> create_scalable_szone 最终我们创建了 szone_t 类型的对象,通过类型转换,得到了我们的 default_zone。

1
2
3
4
malloc_zone_t *
create_scalable_zone(size_t initial_size, unsigned debug_flags) {
return (malloc_zone_t *) create_scalable_szone(initial_size, debug_flags);
}

“malloc”

上文代码段中写道malloc 中调用了 malloc_zone_malloc, 看看malloc_zone_malloc 的实现是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void *
malloc_zone_malloc(malloc_zone_t *zone, size_t size)
{
MALLOC_TRACE(TRACE_malloc | DBG_FUNC_START, (uintptr_t)zone, size, 0, 0);
void *ptr;
if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
internal_check();
}
if (size > MALLOC_ABSOLUTE_MAX_SIZE) {
return NULL;
}
ptr = zone->malloc(zone, size); // if lite zone is passed in then we still call the lite methods
if (malloc_logger) {
malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE, (uintptr_t)zone, (uintptr_t)size, 0, (uintptr_t)ptr, 0);
}
MALLOC_TRACE(TRACE_malloc | DBG_FUNC_END, (uintptr_t)zone, size, (uintptr_t)ptr, 0);
return ptr;
}

其分配实现是 zone->malloc 根据之前的分析,就是szone_t结构体对象中对应的malloc实现。

在创建szone之后,做了一系列如下的初始化操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Initialize the security token.
szone->cookie = (uintptr_t)malloc_entropy[0];
szone->basic_zone.version = 9;
szone->basic_zone.size = (void *)szone_size;
szone->basic_zone.malloc = (void *)szone_malloc;
szone->basic_zone.calloc = (void *)szone_calloc;
szone->basic_zone.valloc = (void *)szone_valloc;
szone->basic_zone.free = (void *)szone_free;
szone->basic_zone.realloc = (void *)szone_realloc;
szone->basic_zone.destroy = (void *)szone_destroy;
szone->basic_zone.batch_malloc = (void *)szone_batch_malloc;
szone->basic_zone.batch_free = (void *)szone_batch_free;
szone->basic_zone.introspect = (struct malloc_introspection_t *)&szone_introspect;
szone->basic_zone.memalign = (void *)szone_memalign;
szone->basic_zone.free_definite_size = (void *)szone_free_definite_size;
szone->basic_zone.pressure_relief = (void *)szone_pressure_relief;

在magazine_malloc.c有着对应的实现。

malloc_logger

malloc_logger 在 libmalloc 中的以下方法内被调用 malloc_zone_malloc malloc_zone_calloc malloc_zone_valloc malloc_zone_realloc malloc_zone_free malloc_zone_free_definite_size malloc_zone_memalign 等函数中被调用,我们 “malloc” 系列的方法都会调用到这些函数

如果你使用 malloc_logger 回调,那么 “malloc” 分配你都可以监控到. OOMDetector 中也针对不同的分配做了不同的处理。因此使用malloc_logger回调是可以监控到 “malloc”分配的,验证了文章开头的猜测。

总结

以malloc_logger为线索,探究了一下libmalloc的源码,确定了OOMDetector的原理。当然最重要的是建立对 iOS 内存分配的整体理解。 本文只写了对”malloc”内存分配的理解,以后有时间,会写一下 XNU内存管理相关的文章。

script>