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

  • 在上一篇源码工程的基础上进行下面的分析

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字节对齐算法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容