iOS libMalloc源码分析-Zone

1,544 阅读9分钟

最近因为工作需要,对iOS底层堆内存分配器libMalloc的开源版本(libmalloc-409.40.6)进行了学习。作为一个使用十分广泛的内存分配器,基于开源代码,研究相关机制,对于iOS内存管理的理解,具有较大价值。和其他内存管理算法类似,libMalloc也需要考虑性能,例如:

  1. 高效复用,减少内存浪费

  2. 降低内存分配与回收的时间复杂度

通过阅读分析代码,我们可以学习借鉴到一些其中的设计思路。

关于libMalloc开源代码的分析也有很多资料,本文借鉴了部分文章的分析内容,主要还是基于代码的理解,有理解错误的地方欢迎交流指出。

zone

zone是libMalloc库管理内存的基础概念,malloc提供的最常用的内存操作API,例如malloc、free、realloc等,都是通过zone来实现的。例如,在malloc.c文件中找到malloc函数的实现

void *malloc(size_t size)
{
    return _malloc_zone_malloc(default_zone, size, MZ_POSIX);
}

内部调用_malloc_zone_malloc函数,并且传递了3个参数,其中size是业务传递进来的内存分配大小。另外2个参数的含义如下:

static void *_malloc_zone_malloc(malloc_zone_t *zone, size_t size, malloc_zone_options_t mzo)
{
    //...
    return zone->malloc(zone, size);
}

第3个参数是malloc_zone_options_t类型mzo,在malloc_set_errno_fast函数中使用,用于标记错误码的逻辑。

__options_decl(malloc_zone_options_t, unsigned, {
    MZ_NONE  = 0x0,
    MZ_POSIX = 0x1,
    MZ_C11   = 0x2,
});

static MALLOC_INLINE void malloc_set_errno_fast(malloc_zone_options_t mzo, int err)
{
    if (mzo & MZ_POSIX) {
#if TARGET_OS_SIMULATOR
        errno = err;
#else
        (*_pthread_errno_address_direct()) = err;
#endif
    }
}

其中err是错误码的值,标记内存分配失败的原因。例如:

#define ENOMEM          12              /* Cannot allocate memory */
#define EACCES          13              /* Permission denied */
#define EFAULT          14              /* Bad address */

第一个参数是malloc_zone_t的结构体指针zone,通过zone->malloc实现malloc分配。首先分析zone的数据结构定义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); /* zone is destroyed and all memory reclaimed */
    const char  *zone_name;

    /* Optional batch callbacks; these may be NULL */
    unsigned    (* MALLOC_ZONE_FN_PTR(batch_malloc))(struct _malloc_zone_t *zone, size_t size, void **results, unsigned num_requested); /* given a size, returns pointers capable of holding that size; returns the number of pointers allocated (maybe 0 or less than num_requested) */
    void    (* MALLOC_ZONE_FN_PTR(batch_free))(struct _malloc_zone_t *zone, void **to_be_freed, unsigned num_to_be_freed); /* frees all the pointers in to_be_freed; note that to_be_freed may be overwritten during the process */

    struct malloc_introspection_t   * MALLOC_INTROSPECT_TBL_PTR(introspect);
    unsigned    version;
        
    /* aligned memory allocation. The callback may be NULL. Present in version >= 5. */
    void *(* MALLOC_ZONE_FN_PTR(memalign))(struct _malloc_zone_t *zone, size_t alignment, size_t size);
    
    /* free a pointer known to be in zone and known to have the given size. The callback may be NULL. Present in version >= 6.*/
    void (* MALLOC_ZONE_FN_PTR(free_definite_size))(struct _malloc_zone_t *zone, void *ptr, size_t size);

    /* Empty out caches in the face of memory pressure. The callback may be NULL. Present in version >= 8. */
    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;
  • reserved1、reserved2:保留字段。

  • 函数指针字段:可以理解为内存相关操作的入口函数地址,malloc库对外提供的API,内部会调用zone的对应函数实现,例如size、malloc、calloc、valloc、free、realloc、destroy等。

  • zone_name:zone的名称。

  • version:zone的版本,控制部分逻辑层实现。

  • introspect:malloc_introspection_t结构体指针,处理zone的内部逻辑,例如地址校验、log、统计、锁同步,如下:

default_zone

_malloc_zone_malloc接收了default_zone作为zone,default_zone的初始化代码如下:

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,
    10,
    default_zone_memalign,
    default_zone_free_definite_size,
    default_zone_pressure_relief,
    default_zone_malloc_claimed_address,
};

static malloc_zone_t *default_zone = &virtual_default_zone.malloc_zone;

default_zone是virtual_default_zone_t结构体virtual_default_zone的malloc_zone字段,该结构体包含malloc_zone_t数据malloc_zone和uint8_t的pad数组两部分。virtual_default_zone是静态变量,位于__DATA段的__v_zone section中。default_zone的各个函数用于malloc API的具体实现,例如:

  • malloc:default_zone_malloc

  • calloc:default_zone_calloc

  • valloc:default_zone_valloc

  • free:default_zone_free

查看default_zone_malloc。

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();
}

首先调用runtime_default_zone返回合适的zone,然后使用该zone执行malloc逻辑。runtime_default_zone内部优先使用lite_zone,否则调用inline_malloc_default_zone创建一个zone返回。从这里可以看出,default_zone不负责具体逻辑,而是处理入口逻辑的zone。真正实行逻辑的zone由runtime_default_zone函数返回。

从malloc API到具体的zone,用下图表示:

whiteboard_exported_image (1).png

初始化

inline_malloc_default_zone的实现如下:

MALLOC_ALWAYS_INLINE static inline void _malloc_initialize_once(void)
{
    os_once(&_malloc_initialize_pred, NULL, _malloc_initialize);
}

static inline malloc_zone_t *inline_malloc_default_zone(void)
{
    _malloc_initialize_once();
    return malloc_zones[0];
}

执行初始化zone的操作,代码如下:

static void _malloc_initialize(const char *apple[], const char *bootargs)
{
    phys_ncpus = *(uint8_t *)(uintptr_t)_COMM_PAGE_PHYSICAL_CPUS;
    logical_ncpus = *(uint8_t *)(uintptr_t)_COMM_PAGE_LOGICAL_CPUS;
    //...

    set_flags_from_environment();
    
    initial_scalable_zone = create_scalable_zone(0, malloc_debug_flags);
    malloc_set_zone_name(initial_scalable_zone, DEFAULT_MALLOC_ZONE_STRING);
    malloc_zone_register_while_locked(initial_scalable_zone, /*make_default=*/true);
    
#if CONFIG_NANOZONE
    nano_common_configure();
    malloc_zone_t *helper_zone = initial_scalable_zone;
    if (_malloc_engaged_nano == NANO_V2) {
        initial_nano_zone = nanov2_create_zone(helper_zone, malloc_debug_flags);
    }
    if (initial_nano_zone) {
        malloc_set_zone_name(initial_nano_zone, DEFAULT_MALLOC_ZONE_STRING);
        malloc_set_zone_name(helper_zone, MALLOC_HELPER_ZONE_STRING);
        malloc_zone_register_while_locked(initial_nano_zone, /*make_default=*/true);
    }
#endif

    initial_num_zones = malloc_num_zones;
    //...
}

整体流程分为:

  1. 初始化环境变量

  2. 创建scalablezone,该zone是处理malloc内存的默认zone。

  3. 如果开启CONFIG_NANOZONE宏,则创建nanozone,V2版本重新实现nanozone的逻辑。

scalablezone

scalablezone是一个较为复杂的zone,针对要处理的内存size,采用tiny、small、medium、large四种不同的策略管理,其数据类型szone_t是malloc_zone_t结构的扩展。

typedef struct szone_s {
    malloc_zone_t basic_zone
    uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)];

    unsigned long cpu_id_key;
    unsigned debug_flags;
    void *log_address;

    struct rack_s tiny_rack;
    struct rack_s small_rack;
    struct rack_s medium_rack;

    _malloc_lock_s large_szone_lock MALLOC_CACHE_ALIGN; // One customer at a time for large
    unsigned num_large_objects_in_use;
    unsigned num_large_entries;
    large_entry_t *large_entries;
    size_t num_bytes_in_large_objects;

#if CONFIG_LARGE_CACHE
    int large_entry_cache_oldest;
    int large_entry_cache_newest;
    large_entry_t large_entry_cache[LARGE_ENTRY_CACHE_SIZE_HIGH]; // "death row" for large malloc/free
    int large_cache_depth;
    size_t large_cache_entry_limit;
    boolean_t large_legacy_reset_mprotect;
    size_t large_entry_cache_reserve_bytes;
    size_t large_entry_cache_reserve_limit;
    size_t large_entry_cache_bytes; // total size of death row, bytes
#endif
    //...
} szone_t;

主要包含3部分:

  • basic_zone:基础malloc_zone_t类型,定义了内存操作的入口函数地址。

  • rack_s:tiny_rack、small_rack、medium_rack,tiny、small、medium内存策略的数据结构,包含策略所需的字段。

  • large策略相关字段:包括large内存统计信息、管理内存的数据结构,以及cache逻辑相关字段。

相应的,通过create_scalable_zone函数初始化scalablezone。

malloc_zone_t *create_scalable_zone(size_t initial_size, unsigned debug_flags) {
    return (malloc_zone_t *) create_scalable_szone(initial_size, debug_flags);
}

szone_t *
create_scalable_szone(size_t initial_size, unsigned debug_flags)
{
    szone_t *szone;
    szone = mvm_allocate_pages(SZONE_PAGED_SIZE, 0, DISABLE_ASLR, VM_MEMORY_MALLOC);
    if (!szone) {
        return NULL;
    }
    //...
    unsigned int max_mags = mag_max_magazines();
    uint32_t num_magazines = (max_mags > 1) ? MIN(max_mags, TINY_MAX_MAGAZINES) : 1;
    rack_init(&szone->tiny_rack, RACK_TYPE_TINY, num_magazines, debug_flags);
    rack_init(&szone->small_rack, RACK_TYPE_SMALL, num_magazines, debug_flags);
    
#if CONFIG_LARGE_CACHE
    //...  
#endif
    szone->cookie = (uintptr_t)malloc_entropy[0];
    szone->basic_zone.version = 13;
    szone->basic_zone.size = (void *)szone_size;
    szone->basic_zone.malloc = (void *)szone_malloc;
    szone->basic_zone.calloc = (void *)szone_calloc;
    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;
    szone->basic_zone.claimed_address = (void *)szone_claimed_address;
    szone->basic_zone.try_free_default = (void *)szone_try_free_default;
    //...
    return szone;
}
  1. 初始化basic_zone相关字段,定义内存操作的入口函数地址。

  2. 初始化rack_s数据,初始化tiny、small、medium等策略相关字段。

  3. 初始化large策略缓存逻辑相关字段。

scalablezone的详细介绍可以参考iOS libMalloc源码分析-ScalableZone

nanozone

实际场景中,libmalloc参与管理的小内存(256字节)数量巨大且频繁,如果管理不好,将造成很大的内存性能隐患,而scalablezone在策略上更为通用,没有定制优化这部分内存的管理,因此libmalloc提供了nanozone来专门管理这部分内存,源码包含v1和v2版本的实现。这里分析一下V2版本的相关逻辑,初始化相关逻辑:

#if CONFIG_NANOZONE
    nano_common_configure();
    malloc_zone_t *helper_zone = initial_scalable_zone;
    if (_malloc_engaged_nano == NANO_V2) {
        initial_nano_zone = nanov2_create_zone(helper_zone, malloc_debug_flags);
    }

    if (initial_nano_zone) {
        malloc_set_zone_name(initial_nano_zone, DEFAULT_MALLOC_ZONE_STRING);
        malloc_set_zone_name(helper_zone, MALLOC_HELPER_ZONE_STRING);
        malloc_zone_register_while_locked(initial_nano_zone, /*make_default=*/true);
    }
#endif
  1. nano_common_configure函数设置一些nanozone的全局字段;

  2. 将scalablezone作为helper_zone,nanov2_create_zone初始化nanozone。

  3. 设置zone name,注册nanozone为默认zone。

nanozone的数据结构和scalablezone类似,除了basic_zone基础字段,也包含自身逻辑相关字段如下:

typedef struct nanozonev2_s {
    malloc_zone_t       basic_zone;
    uint8_t             pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)];
    nanov2_block_meta_t *current_block[NANO_SIZE_CLASSES][MAX_CURRENT_BLOCKS];
    _malloc_lock_s     current_block_lock[NANO_SIZE_CLASSES][MAX_CURRENT_BLOCKS];
    uint16_t           delegate_allocations;
    unsigned            debug_flags;
    uint64_t            aslr_cookie;
    uint64_t            aslr_cookie_aligned;
    uintptr_t           slot_freelist_cookie;
    malloc_zone_t       *helper_zone;
    _malloc_lock_s      blocks_lock;
    _malloc_lock_s      regions_lock;
    nanov2_region_t     *first_region_base;
    os_atomic(nanov2_arena_t *) current_region_next_arena;
    _malloc_lock_s      madvise_lock;
    nanov2_statistics_t statistics;
} nanozonev2_t;

nanov2_create_zone函数创建nanozonev2。

malloc_zone_t *nanov2_create_zone(malloc_zone_t *helper_zone, unsigned debug_flags)
{
    nanozonev2_t *nanozone = nano_common_allocate_based_pages(
            NANOZONEV2_ZONE_PAGED_SIZE, 0, 0, VM_MEMORY_MALLOC, 0);
    if (!nanozone) {
        _malloc_engaged_nano = NANO_NONE;
        return NULL;
    }
    nanozone->basic_zone.version = 13;
    nanozone->basic_zone.size = OS_RESOLVED_VARIANT_ADDR(nanov2_size);
    nanozone->basic_zone.malloc = OS_RESOLVED_VARIANT_ADDR(nanov2_malloc);
    nanozone->basic_zone.calloc = OS_RESOLVED_VARIANT_ADDR(nanov2_calloc);
    nanozone->basic_zone.valloc = (void *)nanov2_valloc;
    nanozone->basic_zone.free = OS_RESOLVED_VARIANT_ADDR(nanov2_free);
    nanozone->basic_zone.realloc = OS_RESOLVED_VARIANT_ADDR(nanov2_realloc);
    //...
    nanozone->helper_zone = helper_zone;
    //...
    return (malloc_zone_t *)nanozone;
}

这是设置内存管理的入口函数,以及nanozone逻辑的相关字段,详细介绍可以参考iOS libMalloc源码分析-NanoZone。其中scalablezone作为nanozone的helper_zone。

最后,调用malloc_zone_register_while_locked设置nanzone为默认zone。

MALLOC_NOEXPORT void malloc_zone_register_while_locked(malloc_zone_t *zone, bool make_default)
{
    //...
    if (make_default) {
        memmove(&malloc_zones[1], &malloc_zones[0], malloc_num_zones * sizeof(malloc_zone_t *));
        malloc_zones[0] = zone;
    } else {
        malloc_zones[malloc_num_zones] = zone;
    }
}

内存策略

初始化操作完成后,接下来分析内存操作中,是如何使用和管理zone的。

分配

malloc

接着分析default_zone_malloc的逻辑,

static void *default_zone_malloc(malloc_zone_t *zone, size_t size)
{
    zone = runtime_default_zone();
    return zone->malloc(zone, size);
}

如果开启CONFIG_NANOZONE的宏,这里的zone->malloc是nanov2_malloc函数。

#define NANO_MAX_SIZE           256 
MALLOC_NOEXPORT void * nanov2_malloc(nanozonev2_t *nanozone, size_t size)
{
    size_t rounded_size = _nano_common_good_size(size);
    if (rounded_size <= NANO_MAX_SIZE) {
        //nanozone分配逻辑
    }
    return nanozone->helper_zone->malloc(nanozone->helper_zone, size);
}

如果大于256字节或者nano分配失败时,使用scalablezone分配。

calloc

类似的,分析一下calloc的流程:

void *calloc(size_t num_items, size_t size)
{
    return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}

static void *_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
        malloc_zone_options_t mzo)
{
    //...
    return zone->calloc(zone, num_items, size);
}

调用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);
}

nanozone的calloc函数nanov2_calloc。

void *nanov2_calloc(nanozonev2_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;
    }
    size_t rounded_size = _nano_common_good_size(total_bytes);
    if (total_bytes <= NANO_MAX_SIZE) {
        //...
    }
    return nanozone->helper_zone->calloc(nanozone->helper_zone, 1, total_bytes);
}

如果大于256字节或者nano分配失败时,使用scalablezone分配。

回收

回收逻辑较分配逻辑复杂一些,涉及到zone的选择。

void free(void *ptr)
{
    if (!ptr) {
        return;
    }
    if (os_unlikely(malloc_instrumented ||
                malloc_check_start ||
                malloc_logger ||
                lite_zone ||
                malloc_num_zones == 0 ||
                malloc_zones[0]->version < 13 ||
                !malloc_zones[0]->try_free_default)) {
        find_zone_and_free(ptr, false);
        return;
    }
    malloc_zone_t *zone0 = malloc_zones[0];
    zone0->try_free_default(zone0, ptr);
}

如果未实现try_free_default,则find_zone_and_free函数。

void find_zone_and_free(void *ptr, bool known_non_default)
{
    malloc_zone_t *zone;
    size_t size;
    if (!ptr) {
        return;
    }

    zone = find_registered_zone(ptr, &size, known_non_default);
    if (!zone) {
        //...
    } else if (zone->version >= 6 && zone->free_definite_size) {
        malloc_zone_free_definite_size(zone, ptr, size);
    } else {
        malloc_zone_free(zone, ptr);
    }
}
  1. 调用find_registered_zone函数根据要回收的内存地址ptr,选择合适的zone,具体实现如下:

    遍历malloc_zones数组,根据ptr查找对应的zone,这里使用zone->size方法来判断,是因为zone分配内存时,内部会存储ptr的size信息,如果zone分配了该内存,则可以通过size方法找到该内存对应的size信息,否则返回0。因此size方法可以作为某个zone是否分配了某块内存的判断方法。

    遍历的过程中,判断has_default_zone0逻辑

    如果没有注册新zone为malloc_zones[0],返回default_zone。否则返回注册的zone。returned_size存储对应匹配到的size。

  2. 如果zone实现了free_definite_size,则调用该方法,否则调用free方法。

  3. 默认返回default_zone的对应方法。

free

如果开启CONFIG_NANOZONE的宏,调用nanozonev2的方法。

MALLOC_NOEXPORT void nanov2_free(nanozonev2_t *nanozone, void *ptr)
{
    _nanov2_free(nanozone, ptr, false);
}

static void _nanov2_free(nanozonev2_t *nanozone, void *ptr, bool try)
{
    if (ptr) {
        nanov2_size_class_t size_class;
        nanov2_block_meta_t *block_metap;
        size_t size = nanov2_pointer_size_inline(nanozone, ptr, FALSE,
                &size_class, &block_metap);
        if (size) {
            if (malloc_zero_on_free) {
                if (size > sizeof(nanov2_free_slot_t)) {
                    nanov2_bzero((char *)ptr + sizeof(nanov2_free_slot_t),
                            size - sizeof(nanov2_free_slot_t));
                }
            }
            nanov2_block_meta_t *madvise_block_metap = nanov2_free_to_block_inline(
                    nanozone, ptr, size_class, block_metap);
            if (madvise_block_metap) {
                nanov2_madvise_block(nanozone, madvise_block_metap, size_class,
                        SLOT_CAN_MADVISE);
            }
            return;
        }
    }
    return try ? nanozone->helper_zone->try_free_default(nanozone->helper_zone, ptr) :
            nanozone->helper_zone->free(nanozone->helper_zone, ptr);
}

调用nanov2_pointer_size_inline获取ptr的size,然后执行nanov2_free_to_block_inline回收逻辑,具体参考iOS libMalloc源码分析-NanoZone。如果size信息未获取,则执行helper_zone(scalablezone)的free逻辑。

free_definite_size

逻辑和_nanov2_free类似,执行nanov2_free_to_block_inline回收。

MALLOC_NOEXPORT void nanov2_free_definite_size(nanozonev2_t *nanozone, void *ptr, size_t size)
{
    if (ptr && nanov2_has_valid_signature(ptr)) {
        nanov2_size_class_t size_class = nanov2_size_class_from_size(size);

        if (malloc_zero_on_free) {
            if (size_class != 0) {
                nanov2_bzero((char *)ptr + sizeof(nanov2_free_slot_t),
                        size - sizeof(nanov2_free_slot_t));
            }
        }

        nanov2_block_meta_t *madvise_block_metap = nanov2_free_to_block_inline(
                nanozone, ptr, size_class, NULL);
        if (madvise_block_metap) {
            nanov2_madvise_block(nanozone, madvise_block_metap, size_class,
                    SLOT_CAN_MADVISE);
        }
        return;
    }
    return nanozone->helper_zone->free_definite_size(nanozone->helper_zone, ptr,
            size);
}

如果size信息未获取,则执行helper_zone(scalablezone)的free_definite_size逻辑。

其他

realloc

realloc更为复杂一些,根据传入的参数决定后续流程

void *realloc(void *in_ptr, size_t new_size)
{
    void *retval = NULL;
    void *old_ptr;
    malloc_zone_t *zone;
    old_ptr = (new_size == 0) ? NULL : in_ptr;
    if (!old_ptr) {
        retval = malloc_zone_malloc(default_zone, new_size);
    } else {
        zone = find_registered_zone(old_ptr, NULL, false);
        if (!zone) {
            int flags = MALLOC_REPORT_DEBUG | MALLOC_REPORT_NOLOG;
            if (malloc_debug_flags & (MALLOC_ABORT_ON_CORRUPTION | MALLOC_ABORT_ON_ERROR)) {
                flags = MALLOC_REPORT_CRASH | MALLOC_REPORT_NOLOG;
            }
            malloc_report(flags, "*** error for object %p: pointer being realloc'd was not allocated\n", in_ptr);
        } else {
            retval = malloc_zone_realloc(zone, old_ptr, new_size);
        }
    }

    if (retval == NULL) {
        malloc_set_errno_fast(MZ_POSIX, ENOMEM);
    } else if (new_size == 0) {
        free(in_ptr);
    }
    return retval;
}

整理逻辑如下:

  1. 如果in_ptr为NULL或者new_size为0,直接使用defaultzone的malloc逻辑。

  2. 否则考虑先回收再分配,先查找zone,再执行malloc_zone_realloc逻辑,例如defaultzone的realloc。

void *malloc_zone_realloc(malloc_zone_t *zone, void *ptr, size_t size)
{
    //...
    new_ptr = zone->realloc(zone, ptr, size);
    //...
    return new_ptr;
}
  1. 如果失败且new_size为0,执行free逻辑。

另外,libmalloc还对外提供了一些其他接口,例如:

malloc_size

返回ptr内存的在malloc库中分配的size。

size_t malloc_size(const void *ptr)
{
    size_t size = 0;
    if (!ptr) {
        return size;
    }
    (void)find_registered_zone(ptr, &size, false);
    return size;
}

使用find_registered_zone方法获取size,内部调用zone->size返回,各个zone会实现对应的size方法。

因此,整体流程如图:

whiteboard_exported_image.png

自定义zone

这套机制下,libmalloc允许外部注册一个自定义的zone,注册步骤分为2步:

  1. 自定义zone,设置基本信息,例如zone name,各接口实现函数,例如malloc、realloc、free等。

  2. 调用malloc_zone_register函数注册新zone,新zone默认加入malloc_zones数组末尾。

相对应,也可以unregister一个zone。

void malloc_zone_unregister(malloc_zone_t *z)
{
    for (index = 0; index < malloc_num_zones; ++index) {
        if (z != malloc_zones[index]) {
            continue;
        }
        size_t protect_size = malloc_num_zones_allocated * sizeof(malloc_zone_t *);
        mprotect(malloc_zones, protect_size, PROT_READ | PROT_WRITE);
        
        malloc_zones[index] = malloc_zones[malloc_num_zones - 1];
        --malloc_num_zones;

        mprotect(malloc_zones, protect_size, PROT_READ);
        initial_num_zones = MAX(MIN(malloc_num_zones, initial_num_zones), 1);
        //...
        return;
    }
 }

遍历malloc_zones数组,匹配成功要unregister的zone后,将其替换为malloc_zones数组中末尾的zone,总个数减1。