美文网首页
iOS底层系列08 -- malloc与calloc源码分析

iOS底层系列08 -- malloc与calloc源码分析

作者: YanZi_33 | 来源:发表于2021-02-08 17:00 被阅读0次
  • 在上一篇源码工程的基础上进行下面的分析

malloc的主流程

#import <Foundation/Foundation.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        char *m;
        m = (char *)(malloc(24)); //动态分配24个字节
        NSLog(@"所占大小%lu",malloc_size(m));
    }
    return 0;
}
  • m = (char *)(malloc(24)); 所在代码行打下断点;工程运行在此断点停下;然后Xcode设置Debug -> Debug Workflow -> Always show Disassembly 进入汇编代码界面,然后点击step into按钮进入函数内部,可以看到汇编代码如下:
Snip20210208_110.png
  • 内部执行的是 malloc.c文件中malloc_zone_malloc函数,具体底层代码如下:
void * malloc(size_t size)
{
    void *retval;
    retval = malloc_zone_malloc(default_zone, size);
    if (retval == NULL) {
        errno = ENOMEM;
    }
    return retval;
}
  • 传入的参数default_zone其实是一个“假的”zone,它是malloc_zone_t类型。它存在的目的就是要引导程序进入一个创建真正的 zone 的流程;
  • 然后进入malloc_zone_malloc函数内部;
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;
}
  • 其核心代码为 ptr = zone->malloc(zone, size);其中zone就是default_zone(virtual_default_zone.malloc_zone)是malloc_zone_t类型;
  • 当断点执行到ptr = zone->malloc(zone, size)时;可以通过下面两个方式知道接下来的执行逻辑:
    a> 点击step into按钮进入函数内部,就来到了default_zone_malloc函数;
    b> 在控制台输入LLDB命令p zone->calloc查找源码实现,控制台输出 如下:
Snip20210208_112.png
static void * default_zone_malloc(malloc_zone_t *zone, size_t size)
{
    zone = runtime_default_zone();
    return zone->malloc(zone, size);
}
  • zone = runtime_default_zone();这里才是真正创建zone;通过runtime_default_zone()创建真正的zone,然后覆盖传进来的default_zone
static inline malloc_zone_t *runtime_default_zone()
 {
    return (lite_zone) ? lite_zone : inline_malloc_default_zone();
}
  • 当断点执行到return (lite_zone) ? lite_zone : inline_malloc_default_zone();时,点击step into按钮进入函数内部,就来到了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_initialize_once(),继续跟踪
static inline void _malloc_initialize_once(void)
{
    os_once(&_malloc_initialize_pred, NULL, _malloc_initialize);
}
  • 其内部执行os_once函数;参数中传入了接下来要执行的函数指针即_malloc_initialize;继续执行会来到_malloc_initialize函数内部,代码较多做了简化只保留核心逻辑如下所示:
static void
_malloc_initialize(void *context __unused)
{
    MALLOC_LOCK();
    unsigned n;
    malloc_zone_t *zone = NULL;

#if CONFIG_NANOZONE
    nano_common_configure();
    //创建helper_zone
    malloc_zone_t *helper_zone = create_scalable_zone(0, malloc_debug_flags);
    //创建nanozone_t
    zone = nano_create_zone(helper_zone, malloc_debug_flags);
    
    if (zone) {
        malloc_zone_register_while_locked(zone);
        malloc_zone_register_while_locked(helper_zone);
        //helper_zone申请内存空间
        malloc_set_zone_name(zone, DEFAULT_MALLOC_ZONE_STRING);
        //nanozone_t申请内存空间
        malloc_set_zone_name(helper_zone, MALLOC_HELPER_ZONE_STRING);
    } else {
        zone = helper_zone;
        malloc_zone_register_while_locked(zone);
        malloc_set_zone_name(zone, DEFAULT_MALLOC_ZONE_STRING);
    }
#else
    zone = create_scalable_zone(0, malloc_debug_flags);
    malloc_zone_register_while_locked(zone);
    malloc_set_zone_name(zone, DEFAULT_MALLOC_ZONE_STRING);
#endif

    initial_default_zone = zone;

    if (n != 0) {
        unsigned protect_size = malloc_num_zones_allocated * sizeof(malloc_zone_t *);
        malloc_zone_t *hold = malloc_zones[0];

        if (hold->zone_name && strcmp(hold->zone_name, DEFAULT_MALLOC_ZONE_STRING) == 0) {
            malloc_set_zone_name(hold, NULL);
        }

        mprotect(malloc_zones, protect_size, PROT_READ | PROT_WRITE);
        malloc_zones[0] = malloc_zones[n];
        malloc_zones[n] = hold;
        mprotect(malloc_zones, protect_size, PROT_READ);
    }

    ......
    MALLOC_UNLOCK();
}
  • malloc_zone_t *helper_zone = create_scalable_zone(0, malloc_debug_flags);是来创建helper_zone的;helper_zone的本质是szone类型;其内部创建下面单独分析;
  • nano_create_zone()函数是创建nanozone_t;其内部创建下面单独分析;
  • malloc_set_zone_name(zone, DEFAULT_MALLOC_ZONE_STRING)为Helper_zone申请内存空间;内部会调用malloc_zone_malloc;重新再走一遍主流程;
  • malloc_set_zone_name(helper_zone, MALLOC_HELPER_ZONE_STRING),为nanozone_t申请内存空间;内部会调用malloc_zone_malloc;重新再走一遍主流程;
  • 综上所述,这里会创建两种zone分别是szone类型与nanozone_t类型

malloc分支流程 -- nanozone_t的创建

  • 上面说到在_malloc_initialize函数内部会创建一个nanozone_t类型的zone;
    nanozone_t是个结构体,结构如下:
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)];
    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(malloc_zone_t *helper_zone, unsigned debug_flags)
{
    nanozone_t *nanozone;
    int i, j;

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

    //basic_zone结构的成员赋值
    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;
    nanozone->basic_zone.reserved2 = 0;
    
    mprotect(nanozone, sizeof(nanozone->basic_zone), PROT_READ);
    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;
        }
    }

    //初始化安全令牌
    nanozone->cookie = (uintptr_t)malloc_entropy[0] & 0x0000ffffffff0000ULL;
    nanozone->helper_zone = helper_zone;
    return (malloc_zone_t *)nanozone;
}
  • 在创建完nanozone_t之后,会调用malloc_set_zone_name()函数
void
malloc_set_zone_name(malloc_zone_t *z, const char *name)
{
    char *newName;

    mprotect(z, sizeof(malloc_zone_t), PROT_READ | PROT_WRITE);
    if (z->zone_name) {
        free((char *)z->zone_name);
        z->zone_name = NULL;
    }
    if (name) {
        size_t buflen = strlen(name) + 1;
        newName = malloc_zone_malloc(z, buflen);
        if (newName) {
            strlcpy(newName, name, buflen);
            z->zone_name = (const char *)newName;
        } else {
            z->zone_name = NULL;
        }
    }
    mprotect(z, sizeof(malloc_zone_t), PROT_READ);
}
  • 核心malloc_zone_malloc(z, buflen)再次进入主流程的调用;在malloc_zone_malloc()函数内部来到ptr = zone->malloc(zone, size);断点停在这一行,然后使用LLDB命令p zone->calloc`查找源码实现,控制台输出 如下:
Snip20210208_113.png
  • 可以看到接下来会执行nano_malloc.c文件的833行,调用nano_malloc()函数;如下所示:
static void * nano_malloc(nanozone_t *nanozone, size_t size)
{
    if (size <= NANO_MAX_SIZE) {
        void *p = _nano_malloc_check_clear(nanozone, size, 0);
        if (p) {
            return p;
        } else {
            /* FALLTHROUGH to helper zone */
        }
    }

    malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
    return zone->malloc(zone, size);
}
  • 从这里可以看出nanozone_t被限制不超过NANO_MAX_SIZE=256;
  • size满足条件进入_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;
    size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
    mag_index_t mag_index = nano_mag_index(nanozone);

    nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);

    ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));
    if (ptr) {
    
    } else {
        ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
    }

    if (cleared_requested && ptr) {
        memset(ptr, 0, slot_bytes);
    }
    return ptr;
}
  • segregated_size_to_fit()这个函数内部进行了16字节对齐,表明malloc内存分配最终使用的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; //16
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; 
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           
    *pKey = k - 1;                                                  

    return slot_bytes;
}
  • NANO_REGIME_QUANTA_SIZE = 16; SHIFT_NANO_QUANTUM = 4;
  • 位运算的算法:(size + 16 - 1) >> 4 << 4 实现了16字节对齐;

总结:

  • OC对象在分配内存空间时,采用的是16字节对齐算法

相关文章

网友评论

      本文标题:iOS底层系列08 -- malloc与calloc源码分析

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