美文网首页
iOS底层原理02 - 对象malloc流程分析

iOS底层原理02 - 对象malloc流程分析

作者: OOOlive | 来源:发表于2021-08-09 16:36 被阅读0次

    上一篇: iOS底层原理01 - 对象alloc、init、new源码分析
    下一篇: iOS底层原理03 - 对象的本质与isa


    在上篇对象alloc流程中提到了一个核心方法calloc,为对象分配内存空间,其实现源码并不在objc源码中,所以当我们想要研究其内部实现时,无法跳转,其源码在libmalloc

    一、 malloc_zone_t 和 NSZone

    在看calloc流程之前,先理解一下什么是Zone。

    Zone可以被理解为一组内存块,在某个Zone里分配的内存块,会随着这个Zone的销毁而销毁,所以Zone可以加速大量小内存块的集体销毁。不过NSZone实际上已经被苹果抛弃,你可以创建自己的NSZone,然后使用allocWithZone将你的OC对象在这个NSZone上分配,但是你的对象还是会被分配在默认的NSZone里。

    • malloc_zont_t是一个结构体,里面包含了各种函数指针,用来存储malloc、free、calloc各种负责垃圾回收的函数具体实现的地址。
    typedef struct _malloc_zone_t {
        /* Only zone implementors should depend on the layout of this structure;
        Regular callers should use the access functions below */
        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);
    
        /*
         * Checks whether an address might belong to the zone. May be NULL. Present in version >= 10.
         * False positives are allowed (e.g. the pointer was freed, or it's in zone space that has
         * not yet been allocated. False negatives are not allowed.
         */
        boolean_t (* MALLOC_ZONE_FN_PTR(claimed_address))(struct _malloc_zone_t *zone, void *ptr);
    } malloc_zone_t;
    

    二、 malloc流程

    对象malloc流程

    当对象调用[xxx alloc]进入calloc分配内存空间的函数后,就会进入libmalloc中的_malloc_zone_calloc方法:

    static void *
    _malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
            malloc_zone_options_t mzo)
    {
        MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
    
        void *ptr;
        if (malloc_check_start) {
            internal_check();
        }
        
        ptr = zone->calloc(zone, num_items, size);
    
        if (os_unlikely(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);
        if (os_unlikely(ptr == NULL)) {
            malloc_set_errno_fast(mzo, ENOMEM);
        }
        return ptr;
    }
    

    在方法ptr = zone->calloc(zone, num_items, size);中的zone为default_zone,后面会进入default_zone_calloc来获取真正的zone。

    1. 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);
    }
    
    • runtime_default_zone获取真正的zone
    static inline malloc_zone_t *
    runtime_default_zone() {
        return (lite_zone) ? lite_zone : inline_malloc_default_zone();
    }
    

    此时的lite_zone为NULL,故进入inline_malloc_default_zone

    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();
        // malloc_report(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
    static void
    _malloc_initialize(const char *apple[], const char *bootargs)
    {
       ...... - 省略N行无用代码
       
        const uint32_t k_max_zones = 3;
        malloc_zone_t *zone_stack[k_max_zones];
        const char *name_stack[k_max_zones];
        uint32_t num_zones = 0;
    
        initial_scalable_zone = create_scalable_zone(0, malloc_debug_flags);
        zone_stack[num_zones] = initial_scalable_zone;
        name_stack[num_zones] = DEFAULT_MALLOC_ZONE_STRING;
        num_zones++;
    
    #if CONFIG_NANOZONE
        nano_common_configure();
        // 创建helper_zone
        malloc_zone_t *helper_zone = zone_stack[num_zones - 1];
        // 创建nano_zone
        malloc_zone_t *nano_zone = NULL;
        // 使用helper_zone分配内存
        nano_zone = nano_create_zone(helper_zone, malloc_debug_flags);
    
        if (nano_zone) {
            initial_nano_zone = nano_zone;
            zone_stack[num_zones] = nano_zone;
            name_stack[num_zones] = DEFAULT_MALLOC_ZONE_STRING;
            name_stack[num_zones - 1] = MALLOC_HELPER_ZONE_STRING;
            num_zones++;
        }
    #endif
    
        if (pguard_enabled()) {
            malloc_zone_t *wrapped_zone = zone_stack[num_zones - 1];
            zone_stack[num_zones] = pguard_create_zone(wrapped_zone, malloc_debug_flags);
            name_stack[num_zones] = MALLOC_PGUARD_ZONE_STRING;
            // TODO(yln): what is the external contract for zone names?
            num_zones++;
        }
    
        MALLOC_ASSERT(num_zones <= k_max_zones);
        // 缓存default_zone
        initial_default_zone = zone_stack[num_zones - 1];
    
        // 2 separate loops: malloc_set_zone_name already requires a working allocator.
        for (int i = num_zones - 1; i >= 0; i--) malloc_zone_register_while_locked(zone_stack[I]);
        for (int i = num_zones - 1; i >= 0; i--) malloc_set_zone_name(zone_stack[i], name_stack[I]);
    
    }
    
    • 使用真正的zone调用calloc方法

    回到上面default_zone_calloc函数,return zone->calloc(zone, num_items, size); 就是使用nanozone_t调用calloc函数,即进入 nano_malloc

    2. nano_malloc

    下面是nano_malloc的实现:

    static void *
    nano_malloc(nanozone_t *nanozone, size_t size)
    {
       // 判断要开辟的空间size是否小于256,若小的话,进行nanozone_t的malloc
        if (size <= NANO_MAX_SIZE) {
            void *p = _nano_malloc_check_clear(nanozone, size, 0);
            if (p) {
                return p;
            } else {
                /* FALLTHROUGH to helper zone */
            }
        }
        // 否则 进行helper_zone的流程
        malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
        return zone->malloc(zone, size);
    }
    
    • _nano_malloc_check_clear

    当size小于NANO_MAX_SIZE,即256时,调用_nano_malloc_check_clear获取内存指针:

    _nano_malloc_check_clear 核心流程
    • segregated_size_to_fit
      获取加密算法的盐,从该方法可以看出其实质是个16字节的对齐算法:
    static MALLOC_INLINE size_t
    segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
    {
        size_t k, slot_bytes;
    
        if (0 == size) {
            size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
        }
        // size + 15 >> 4 << 4
        // size先右移四位,再左移四位,其实就是对不满16的位数进行了抹零操作,得到的值为16的倍数
        k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
        slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
        *pKey = k - 1;                                                  // Zero-based!
    
        return slot_bytes;
    }
    
    • 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
            // 获取下一地址
            uintptr_t b = OSAtomicAdd64Barrier(slot_bytes, (volatile int64_t *)&(pMeta->slot_bump_addr));
            // 减去slot_bytes获取当前地址
            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 {
                // 不在范围内
                // 已经用尽 返回0
                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后重新尝试
                        _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)) {
                        // segregated_band_grow 申请新的band重新尝试
                        _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;
                    }
                }
            }
        }
    }
    
    

    通过断点调试发现,第一次进入时没有band和缓存,会进入segregated_band_grow方法申请开辟新的band。每个Band固定大小2M,可容纳16个128kb的槽,如果当前Band中的slot耗尽,会向系统申请新的band。

    相关文章

      网友评论

          本文标题:iOS底层原理02 - 对象malloc流程分析

          本文链接:https://www.haomeiwen.com/subject/nbkymltx.html