美文网首页
IOS底层(七): alloc相关: calloc源码分析

IOS底层(七): alloc相关: calloc源码分析

作者: ShawnAlex | 来源:发表于2021-03-27 22:32 被阅读0次

    OC底层源码/原理合集

    建议先看下
    IOS底层(三): alloc相关1.初探 alloc, init, new源码分析

    alloc初始化关键三个步骤计算内存大小instanceSize, 申请内存calloc, 指针关联 initInstanceIsa, 这篇文章重点看一下申请内存calloc

    /***********************************************************************
    * class_createInstance
    * fixme
    * Locking: none
    *
    * Note: this function has been carefully written so that the fastpath
    * takes no branch.
    **********************************************************************/
    static ALWAYS_INLINE id
    _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                                  int construct_flags = OBJECT_CONSTRUCT_NONE,
                                  bool cxxConstruct = true,
                                  size_t *outAllocatedSize = nil)
    {
        ASSERT(cls->isRealized());
    
        // Read class's info bits all at once for performance
        bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
        bool hasCxxDtor = cls->hasCxxDtor();
        bool fast = cls->canAllocNonpointer();
        size_t size;
    
        size = cls->instanceSize(extraBytes);
        if (outAllocatedSize) *outAllocatedSize = size;
    
        id obj;
        
        if (zone) {
            obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
        } else {
            
            obj = (id)calloc(1, size);
            
            
        }
        if (slowpath(!obj)) {
            if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
                return _objc_callBadAllocHandler(cls);
            }
            return nil;
        }
    
        if (!zone && fast) {
            
            obj->initInstanceIsa(cls, hasCxxDtor);
            
        } else {
            // Use raw pointer isa on the assumption that they might be
            // doing something weird with the zone or RR.
            obj->initIsa(cls);
        }
    
        if (fastpath(!hasCxxCtor)) {
            return obj;
        }
    
        construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
        return object_cxxConstructFromClass(obj, cls, construct_flags);
    }
    
    

    calloc 方法点击进入有

    void    *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);
    

    再点击发现无了, GG

    因为calloc源码在libmalloc里面 下载地址

    其实也可以从上边指向看到位于哪个源码


    calloc

    libmalloc 源码中跟一下calloc

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            
            void *p = calloc(1, 40);
            NSLog(@"Hello, World!");
            
        }
        return 0;
    }
    

    进入calloc源码, 全局搜索``也行
    其中default_zone是一个默认的zone, 目的是引导创建真正zone流程

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

    点击进入malloc_zone_calloc方法

    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);方法是我们要找的关键, 点击进入又GG了

    calloc源码

    方法一:

    在这行我们打个断点, 然后p或者 po一下zone->calloc, 查找源码实现位置

    image.png
    因为这里的指针函数必然要赋值

    方法二:

    当然我们也可以通过control + step into 方式进入calloc源码实现位置

    step into

    可看到源码实现是在default_zone_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 = runtime_default_zone(); 为创建真正的zone
    • zone->calloc(zone, num_items, size);真正的zone进行calloc

    我们先看下zone = runtime_default_zone();

    MALLOC_NOEXPORT malloc_zone_t* lite_zone = NULL;
    
    MALLOC_ALWAYS_INLINE
    static inline malloc_zone_t *
    runtime_default_zone() {
        return (lite_zone) ? lite_zone : inline_malloc_default_zone();
    }
    
    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_zones的值发现是NULL,可以得出,此时的zone还未赋值

    zone还未赋值

    回到default_zone_calloc方法,继续执行,断在zone->calloc部分,此时同样可以通过po 一下可看到

    zone->calloc

    可看到会走nano_calloc方法

    全局搜索 nano_calloc, 进入看下源码

    #define NANO_MAX_SIZE           256 /* Buckets sized {16, 32, 48, ..., 256} */
    
    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;
        }
    
        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 */
            }
        }
        malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
        return zone->calloc(zone, 1, total_bytes);
    }
    

    如果开辟空间小于NANO_MAX_SIZE , 则会走_nano_malloc_check_clear方法, 进入_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) {
            unsigned debug_flags = nanozone->debug_flags;
    #if NANO_FREE_DEQUEUE_DILIGENCE
            size_t gotSize;
            nano_blk_addr_t p; // the compiler holds this in a register
    
            p.addr = (uint64_t)ptr; // Begin the dissection of ptr
            if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
                malloc_zone_error(debug_flags, true,
                        "Invalid signature for pointer %p dequeued from free list\n",
                        ptr);
            }
    
            if (mag_index != p.fields.nano_mag_index) {
                malloc_zone_error(debug_flags, true,
                        "Mismatched magazine for pointer %p dequeued from free list\n",
                        ptr);
            }
    
            gotSize = _nano_vet_and_size_of_free(nanozone, ptr);
            if (0 == gotSize) {
                malloc_zone_error(debug_flags, true,
                        "Invalid pointer %p dequeued from free list\n", ptr);
            }
            if (gotSize != slot_bytes) {
                malloc_zone_error(debug_flags, true,
                        "Mismatched size for pointer %p dequeued from free list\n",
                        ptr);
            }
    
            if (!_nano_block_has_canary_value(nanozone, ptr)) {
                malloc_zone_error(debug_flags, true,
                        "Heap corruption detected, free list canary is damaged for %p\n"
                        "*** Incorrect guard value: %lu\n", ptr,
                        ((chained_block_t)ptr)->double_free_guard);
            }
    
    #if defined(DEBUG)
            void *next = (void *)(((chained_block_t)ptr)->next);
            if (next) {
                p.addr = (uint64_t)next; // Begin the dissection of next
                if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
                    malloc_zone_error(debug_flags, true,
                            "Invalid next signature for pointer %p dequeued from free "
                            "list, next = %p\n", ptr, "next");
                }
    
                if (mag_index != p.fields.nano_mag_index) {
                    malloc_zone_error(debug_flags, true,
                            "Mismatched next magazine for pointer %p dequeued from "
                            "free list, next = %p\n", ptr, next);
                }
    
                gotSize = _nano_vet_and_size_of_free(nanozone, next);
                if (0 == gotSize) {
                    malloc_zone_error(debug_flags, true,
                            "Invalid next for pointer %p dequeued from free list, "
                            "next = %p\n", ptr, next);
                }
                if (gotSize != slot_bytes) {
                    malloc_zone_error(debug_flags, true,
                            "Mismatched next size for pointer %p dequeued from free "
                            "list, next = %p\n", ptr, next);
                }
            }
    #endif /* DEBUG */
    #endif /* NANO_FREE_DEQUEUE_DILIGENCE */
    
            ((chained_block_t)ptr)->double_free_guard = 0;
            ((chained_block_t)ptr)->next = NULL; // clear out next pointer to protect free list
        } 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;
    }
    

    这里我们看主要流程
    其中size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); 这里是重点, 是开辟内存算法

    #define SHIFT_NANO_QUANTUM      4
    #define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) 
    
    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
        }
        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;
    }
    

    这里是16字节对齐算法
    重点是这块 k + 15 >> 4 <<4, k + 15先右移4位后左移4位返回

    例如: k = 2, k + 15 = 17
    
    右移四位
    00010001  →  00000001
    
    左移四位
    00000001  →  00010000
    
    00010000 = 16
    

    也可看出这里是个16位对齐内存

    跳过error地方, 我们看这个segregated_next_block方法

    ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
    

    看下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;
                    }
                }
            }
        }
    }
    

    可看出 segregated_next_block主要就是获取内存指针方法

    综上可以看出calloc只是开辟内存空间并返回个指针对象

    相关文章

      网友评论

          本文标题:IOS底层(七): alloc相关: calloc源码分析

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