注册

iOS —— malloc分析(1)

一、malloc_zone_t 分析

这个家伙是一个非常重要的家伙,我们先来看看 malloc_zone_t 的结构

typedef struct _malloc_zone_t {
void *reserved1; /* RESERVED FOR CFAllocator DO NOT USE */
void *reserved2; /* RESERVED FOR CFAllocator DO NOT USE */
size_t (* MALLOC_ZONE_FN_PTR(size))(struct _malloc_zone_t *zone, const void *ptr); /* returns the size of a block or 0 if not in this zone; must be fast, especially for negative answers */
void *(* MALLOC_ZONE_FN_PTR(malloc))(struct _malloc_zone_t *zone, size_t size);
void *(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */
void *(* MALLOC_ZONE_FN_PTR(valloc))(struct _malloc_zone_t *zone, size_t size); /* same as malloc, but block returned is set to zero and is guaranteed to be page aligned */
void (* MALLOC_ZONE_FN_PTR(free))(struct _malloc_zone_t *zone, void *ptr);
void *(* MALLOC_ZONE_FN_PTR(realloc))(struct _malloc_zone_t *zone, void *ptr, size_t size);
void (* MALLOC_ZONE_FN_PTR(destroy))(struct _malloc_zone_t *zone);
const char *zone_name;

unsigned (* MALLOC_ZONE_FN_PTR(batch_malloc))(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested);

struct malloc_introspection_t * MALLOC_INTROSPECT_TBL_PTR(introspect);
unsigned version;

void *(* MALLOC_ZONE_FN_PTR(memalign))(struct _malloc_zone_t *zone, size_t alignment, size_t size);

void (* MALLOC_ZONE_FN_PTR(free_definite_size))(struct _malloc_zone_t *zone, void *ptr, size_t size);

size_t (* MALLOC_ZONE_FN_PTR(pressure_relief))(struct _malloc_zone_t *zone, size_t goal);

boolean_t (* MALLOC_ZONE_FN_PTR(claimed_address))(struct _malloc_zone_t *zone, void *ptr);

} malloc_zone_t;


malloc_zone_t 是一个非常基础结构,里面包含一堆函数指针,用来存储一堆相关的处理函数的具体实现的地址,例如mallocfreerealloc等函数的具体实现。后续会基于malloc_zone_t进行扩展。

二、calloc 的流程

2.1  calloc -> malloc_zone_calloc 的流程


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 其实是一个“假的”zone,同时它也是malloc_zone_t类型。它存在的目的就是要引导程序进入一个创建真正的 zone 的流程。下面来看一下 default_zone 的引导流程。

2.2 default_zone 引导

void * malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

void *ptr;
if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
internal_check();
}

ptr = zone->calloc(zone, num_items, size);

if (malloc_logger) {
malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
}

MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
return ptr;
}
ptr = zone->calloc(zone, num_items, size)此时传进来的 zone 的类型是 上面 calloc 传入的 defaultzone,所以 zone->calloc的调用实现要看defaultzone 的定义。

2.3 defaultzone 的定义

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,
10,
default_zone_memalign,
default_zone_free_definite_size,
default_zone_pressure_relief,
default_zone_malloc_claimed_address,
};
  • 从上面的结构可以看出 defaultzone->calloc 实际的函数实现为 default_zone_calloc
static void *
default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
zone = runtime_default_zone();

return zone->calloc(zone, num_items, size);
}
  • 引导创建真正的 zone
  • 使用真正的 zone 进行 calloc

2.4 zone分析

在创建正在的 zone时,其实系统是有对应的一套创建策略的。在跟踪 runtime_default_zone 方法后,最终会进入如下调用

2bc93ae4a222588a75964a3c49acfb46.png

static void
_malloc_initialize(void *context __unused)
{
...... - 省略多余代码
//创建helper_zone,
malloc_zone_t *helper_zone = create_scalable_zone(0, malloc_debug_flags);
//创建 nano zone
if (_malloc_engaged_nano == NANO_V2) {
zone = nanov2_create_zone(helper_zone, malloc_debug_flags);
} else if (_malloc_engaged_nano == NANO_V1) {
zone = nano_create_zone(helper_zone, malloc_debug_flags);
}
//如果上面的if else if 成立,这进入 nonazone
if (zone) {
malloc_zone_register_while_locked(zone);
malloc_zone_register_while_locked(helper_zone);

// Must call malloc_set_zone_name() *after* helper and nano are hooked together.
malloc_set_zone_name(zone, DEFAULT_MALLOC_ZONE_STRING);
malloc_set_zone_name(helper_zone, MALLOC_HELPER_ZONE_STRING);
} else {
//使用helper_zone分配内存
zone = helper_zone;
malloc_zone_register_while_locked(zone);
malloc_set_zone_name(zone, DEFAULT_MALLOC_ZONE_STRING);
}
//缓存default_zone
initial_default_zone = zone;
.....
}
  • 创建 helper_zone
  • 创建 nano zone
  • 如果上面的 if else if 成立,这进入 nonazone
  • 使用 helper_zone 分配内存
  • 缓存 default_zone

在这里 会存在两种 zone

    1. nanozone_t
    1. scalable_zone

2.5 nanozone_t 分析


typedef struct nano_meta_s {
OSQueueHead slot_LIFO MALLOC_NANO_CACHE_ALIGN;
unsigned int slot_madvised_log_page_count;
volatile uintptr_t slot_current_base_addr;
volatile uintptr_t slot_limit_addr;
volatile size_t slot_objects_mapped;
volatile size_t slot_objects_skipped;
bitarray_t slot_madvised_pages;
// position on cache line distinct from that of slot_LIFO
volatile uintptr_t slot_bump_addr MALLOC_NANO_CACHE_ALIGN;
volatile boolean_t slot_exhausted;
unsigned int slot_bytes;
unsigned int slot_objects;
} *nano_meta_admin_t;

// vm_allocate()'d, so page-aligned to begin with.
typedef struct nanozone_s {
// first page will be given read-only protection
malloc_zone_t basic_zone;
uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)];

// remainder of structure is R/W (contains no function pointers)
// page-aligned
// max: NANO_MAG_SIZE cores x NANO_SLOT_SIZE slots for nano blocks {16 .. 256}
//以Mag、Slot为维度,维护申请的band内存部分 slot 的范围为 1~16
struct nano_meta_s meta_data[NANO_MAG_SIZE][NANO_SLOT_SIZE];//
_malloc_lock_s band_resupply_lock[NANO_MAG_SIZE];
uintptr_t band_max_mapped_baseaddr[NANO_MAG_SIZE];
size_t core_mapped_size[NANO_MAG_SIZE];
unsigned debug_flags;
uintptr_t cookie;
malloc_zone_t *helper_zone;
} nanozone_t;
  • nanozone_t 同样是 malloc_zone_t 类型。在nano_create_zone 函数内部会完成对 calloc等函数的重新赋值。

2.6  nano_create_zone 分析

malloc_zone_t *
nano_create_zone(malloc_zone_t *helper_zone, unsigned debug_flags)
{
nanozone_t *nanozone;
int i, j;
//构造nano zone
/* Note: It is important that nano_create_zone resets _malloc_engaged_nano
* if it is unable to enable the nanozone (and chooses not to abort). As
* several functions rely on _malloc_engaged_nano to determine if they
* should manipulate the nanozone, and these should not run if we failed
* to create the zone.
*/

// MALLOC_ASSERT(_malloc_engaged_nano == NANO_V1);

/* get memory for the zone. */
nanozone = nano_common_allocate_based_pages(NANOZONE_PAGED_SIZE, 0, 0, VM_MEMORY_MALLOC, 0);
if (!nanozone) {
_malloc_engaged_nano = NANO_NONE;
return NULL;
}
//构造对zone 的一些函数进行重新赋值
/* set up the basic_zone portion of the nanozone structure */
nanozone->basic_zone.version = 10;
nanozone->basic_zone.size = (void *)nano_size;
nanozone->basic_zone.malloc = (debug_flags & MALLOC_DO_SCRIBBLE) ? (void *)nano_malloc_scribble : (void *)nano_malloc;
nanozone->basic_zone.calloc = (void *)nano_calloc;
nanozone->basic_zone.valloc = (void *)nano_valloc;
nanozone->basic_zone.free = (debug_flags & MALLOC_DO_SCRIBBLE) ? (void *)nano_free_scribble : (void *)nano_free;
nanozone->basic_zone.realloc = (void *)nano_realloc;
nanozone->basic_zone.destroy = (void *)nano_destroy;
nanozone->basic_zone.batch_malloc = (void *)nano_batch_malloc;
nanozone->basic_zone.batch_free = (void *)nano_batch_free;
nanozone->basic_zone.introspect = (struct malloc_introspection_t *)&nano_introspect;
nanozone->basic_zone.memalign = (void *)nano_memalign;
nanozone->basic_zone.free_definite_size = (debug_flags & MALLOC_DO_SCRIBBLE) ? (void *)nano_free_definite_size_scribble
: (void *)nano_free_definite_size;

nanozone->basic_zone.pressure_relief = (void *)nano_pressure_relief;
nanozone->basic_zone.claimed_address = (void *)nano_claimed_address;

nanozone->basic_zone.reserved1 = 0; /* Set to zero once and for all as required by CFAllocator. */
nanozone->basic_zone.reserved2 = 0; /* Set to zero once and for all as required by CFAllocator. */

mprotect(nanozone, sizeof(nanozone->basic_zone), PROT_READ); /* Prevent overwriting the function pointers in basic_zone. */

/* Nano zone does not support MALLOC_ADD_GUARD_PAGES. */
if (debug_flags & MALLOC_ADD_GUARD_PAGES) {
malloc_report(ASL_LEVEL_INFO, "nano zone does not support guard pages\n");
debug_flags &= ~MALLOC_ADD_GUARD_PAGES;
}

/* set up the remainder of the nanozone structure */
nanozone->debug_flags = debug_flags;

if (phys_ncpus > sizeof(nanozone->core_mapped_size) /
sizeof(nanozone->core_mapped_size[0])) {
MALLOC_REPORT_FATAL_ERROR(phys_ncpus,
"nanozone abandoned because NCPUS > max magazines.\n");
}

/* Initialize slot queue heads and resupply locks. */
OSQueueHead q0 = OS_ATOMIC_QUEUE_INIT;
for (i = 0; i < nano_common_max_magazines; ++i) {
_malloc_lock_init(&nanozone->band_resupply_lock[i]);

for (j = 0; j < NANO_SLOT_SIZE; ++j) {
nanozone->meta_data[i][j].slot_LIFO = q0;
}
}

/* Initialize the security token. */
nanozone->cookie = (uintptr_t)malloc_entropy[0] & 0x0000ffffffff0000ULL; // scramble central 32bits with this cookie

nanozone->helper_zone = helper_zone;

return (malloc_zone_t *)nanozone;
}
  • 构造 nano zone
  • 构造对 zone 的一些函数进行重新赋值
  • Nano zone 不支持 MALLOC_ADD_GUARD_PAGES
  • 建立其余的 nanozone 结构
  • 初始化插槽队列头并重新供应锁
  • 初始化安全令牌。

2.7 nano_calloc 分析

过程参考 defaultzone 。回到上面 default_zone_calloc 函数内。下一步就是使用 nanozone_t 调用 calloc

下面是 nano_calloc 的实现

static void *
nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size)
{
size_t total_bytes;

if (calloc_get_size(num_items, size, 0, &total_bytes)) {
return NULL;
}
// 如果要开辟的空间小于 NANO_MAX_SIZE 则进行nanozone_t的malloc。
if (total_bytes <= NANO_MAX_SIZE) {
void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
if (p) {
return p;
} else {
/* FALLTHROUGH to helper zone */
}
}
//否则就进行helper_zone的流程
malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
return zone->calloc(zone, 1, total_bytes);
}
  • 如果要开辟的空间小于 NANO_MAX_SIZE 则进行
  • 否则就进行 helper_zone 的流程

2.8 _nano_malloc_check_clear分析

这里我们也可以看出使用 nanozone_t 的限制为不超过256B。继续看 _nano_malloc_check_clear



static void *
_nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested)
{
MALLOC_TRACE(TRACE_nano_malloc, (uintptr_t)nanozone, size, cleared_requested, 0);

void *ptr;
size_t slot_key;
// 获取16字节对齐之后的大小,slot_key非常关键,为slot_bytes/16的值,也是数组的二维下下标
size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
//根据_os_cpu_number经过运算获取 mag_index(meta_data的一维索引)
mag_index_t mag_index = nano_mag_index(nanozone);
//确定当前cpu对应的mag和通过size参数计算出来的slot,去对应metadata的链表中取已经被释放过的内存区块缓存
nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);
//检测是否存在已经释放过,可以直接拿来用的内存,已经被释放的内存会缓存在 chained_block_s 链表
//每一次free。同样会根据 index 和slot 的值回去 pMeta,然后把slot_LIFO的指针指向释放的内存。
ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));
if (ptr) {

...省略无关代码

//如果缓存的内存存在,这进行指针地址检查等异常检测,最后返回
//第一次调用malloc时,不会执行这一块代码。
} else {
//没有释放过的内存,所以调用函数 获取内存
ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
}

if (cleared_requested && ptr) {
memset(ptr, 0, slot_bytes); // TODO: Needs a memory barrier after memset to ensure zeroes land first?
}
return ptr;
}
  • 获取16字节对齐之后的大小, slot_key 非常关键,为slot_bytes/16 的值,也是数组的二维下下标

  • 根据 _os_cpu_number 经过运算获取 mag_index ( meta_data 的一维索引)

  • 确定当前 cpu 对应的 mag 和通过 size 参数计算出来的 slot,去对应 metadata 的链表中取已经被释放过的内存区块缓存

  • 检测是否存在已经释放过,可以直接拿来用的内存,已经被释放的内存会缓存在chained_block_s链表

  • 每一次 free。同样会根据  index 和 slot 的值回去 pMeta,然后把 slot_LIFO 的指针指向释放的内存。

  • 如果缓存的内存存在,这进行指针地址检查等异常检测,最后返回

  • 没有释放过的内存,所以调用函数 获取内存

该方法主要是通过 cpu 与 slot 确定 index,从chained_block_s 链表中找出是否存在已经释放过的缓存。如果存在则进行指针检查之后返回,否则进入查询 meta data 或者开辟 band

2.9 segregated_next_block 分析


static MALLOC_INLINE void *
segregated_next_block(nanozone_t *nanozone, nano_meta_admin_t pMeta, size_t slot_bytes, unsigned int mag_index)
{
while (1) {
//当前这块pMeta可用内存的结束地址
uintptr_t theLimit = pMeta->slot_limit_addr; // Capture the slot limit that bounds slot_bump_addr right now
//原子的为pMeta->slot_bump_addr添加slot_bytes的长度,偏移到下一个地址
uintptr_t b = OSAtomicAdd64Barrier(slot_bytes, (volatile int64_t *)&(pMeta->slot_bump_addr));
//减去添加的偏移量,获取当前可以获取的地址
b -= slot_bytes; // Atomic op returned addr of *next* free block. Subtract to get addr for *this* allocation.

if (b < theLimit) { // Did we stay within the bound of the present slot allocation?
//如果地址还在范围之内,则返回地址
return (void *)b; // Yep, so the slot_bump_addr this thread incremented is good to go
} else {
//已经用尽了
if (pMeta->slot_exhausted) { // exhausted all the bands availble for this slot?
pMeta->slot_bump_addr = theLimit;
return 0; // We're toast
} else {
// One thread will grow the heap, others will see its been grown and retry allocation
_malloc_lock_lock(&nanozone->band_resupply_lock[mag_index]);
// re-check state now that we've taken the lock
//多线程的缘故,重新检查是否用尽
if (pMeta->slot_exhausted) {
_malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
return 0; // Toast
} else if (b < pMeta->slot_limit_addr) {
//如果小于最大限制地址,当重新申请一个新的band后,重新尝试while
_malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
continue; // ... the slot was successfully grown by first-taker (not us). Now try again.
} else if (segregated_band_grow(nanozone, pMeta, slot_bytes, mag_index)) {
//申请新的band成功,重新尝试while
_malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
continue; // ... the slot has been successfully grown by us. Now try again.
} else {
pMeta->slot_exhausted = TRUE;
pMeta->slot_bump_addr = theLimit;
_malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
return 0;
}
}
}
}
}
  • 当前这块 pMeta 可用内存的结束地址

  • 原子的为 pMeta->slot_bump_addr 添加 slot_bytes 的长度,偏移到下一个地址

  • b -= slot_bytes 减去添加的偏移量,获取当前可以获取的地址

  • 如果地址还在范围之内,则返回地址 return (void *)b

  • pMeta->slot_exhausted 多线程的缘故,重新检查是否用尽

  • _malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]); 如果小于最大限制地址,当重新申请一个新的 band 后,重新尝试 while

  • _malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);申请新的 band成功,重新尝试 while

如果是第一次调用 segregated_next_block 函数,band 不存在,缓存也不会存在,所以会调用segregated_band_grow。来开辟新的 band




0 个评论

要回复文章请先登录注册