iOS底層原理02 - 對(duì)象malloc流程分析

上一篇: iOS底層原理01 - 對(duì)象alloc棘捣、init乍恐、new源碼分析
下一篇: iOS底層原理03 - 對(duì)象的本質(zhì)與isa


在上篇對(duì)象alloc流程中提到了一個(gè)核心方法calloc,為對(duì)象分配內(nèi)存空間茵烈,其實(shí)現(xiàn)源碼并不在objc源碼中呜投,所以當(dāng)我們想要研究其內(nèi)部實(shí)現(xiàn)時(shí),無法跳轉(zhuǎn)雕拼,其源碼在libmalloc

一悲没、 malloc_zone_t 和 NSZone

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

Zone可以被理解為一組內(nèi)存塊栈戳,在某個(gè)Zone里分配的內(nèi)存塊难裆,會(huì)隨著這個(gè)Zone的銷毀而銷毀,所以Zone可以加速大量小內(nèi)存塊的集體銷毀褂痰。不過NSZone實(shí)際上已經(jīng)被蘋果拋棄,你可以創(chuàng)建自己的NSZone,然后使用allocWithZone將你的OC對(duì)象在這個(gè)NSZone上分配归薛,但是你的對(duì)象還是會(huì)被分配在默認(rèn)的NSZone里匪蝙。

  • malloc_zont_t是一個(gè)結(jié)構(gòu)體逛球,里面包含了各種函數(shù)指針,用來存儲(chǔ)malloc幸海、free屋厘、calloc各種負(fù)責(zé)垃圾回收的函數(shù)具體實(shí)現(xiàn)的地址。
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流程

對(duì)象malloc流程

當(dāng)對(duì)象調(diào)用[xxx alloc]進(jìn)入calloc分配內(nèi)存空間的函數(shù)后瞻凤,就會(huì)進(jìn)入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阀参,后面會(huì)進(jìn)入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();
}

此時(shí)的lite_zone為NULL所刀,故進(jìn)入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();
    // 創(chuàng)建helper_zone
    malloc_zone_t *helper_zone = zone_stack[num_zones - 1];
    // 創(chuàng)建nano_zone
    malloc_zone_t *nano_zone = NULL;
    // 使用helper_zone分配內(nèi)存
    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調(diào)用calloc方法

回到上面default_zone_calloc函數(shù)浮创,return zone->calloc(zone, num_items, size); 就是使用nanozone_t調(diào)用calloc函數(shù)斩披,即進(jìn)入 nano_malloc 讹俊。

2. nano_malloc

下面是nano_malloc的實(shí)現(xiàn):

static void *
nano_malloc(nanozone_t *nanozone, size_t size)
{
   // 判斷要開辟的空間size是否小于256煌抒,若小的話,進(jìn)行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 */
        }
    }
    // 否則 進(jìn)行helper_zone的流程
    malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
    return zone->malloc(zone, size);
}
  • _nano_malloc_check_clear

當(dāng)size小于NANO_MAX_SIZE耳奕,即256時(shí)诬像,調(diào)用_nano_malloc_check_clear獲取內(nèi)存指針:

_nano_malloc_check_clear 核心流程
  • segregated_size_to_fit
    獲取加密算法的鹽坏挠,從該方法可以看出其實(shí)質(zhì)是個(gè)16字節(jié)的對(duì)齊算法:
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先右移四位邪乍,再左移四位庇楞,其實(shí)就是對(duì)不滿16的位數(shù)進(jìn)行了抹零操作,得到的值為16的倍數(shù)
    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
    獲取內(nèi)存指針
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獲取當(dāng)前地址
        b -= slot_bytes; // Atomic op returned addr of *next* free block. Subtract to get addr for *this* allocation.
        // 判斷當(dāng)前地址是否在最大地址范圍內(nèi)
        if (b < theLimit) {   // Did we stay within the bound of the present slot allocation?
            // 在范圍內(nèi)直接返回
            return (void *)b; // Yep, so the slot_bump_addr this thread incremented is good to go
        } else {
            // 不在范圍內(nèi)
            // 已經(jīng)用盡 返回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
                // 重新檢測(cè)
                if (pMeta->slot_exhausted) {
                    _malloc_lock_unlock(&nanozone->band_resupply_lock[mag_index]);
                    return 0; // Toast
                } else if (b < pMeta->slot_limit_addr) {
                    // 重新檢測(cè)在范圍內(nèi),申請(qǐng)一個(gè)新的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 申請(qǐng)新的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;
                }
            }
        }
    }
}

通過斷點(diǎn)調(diào)試發(fā)現(xiàn)烙心,第一次進(jìn)入時(shí)沒有band和緩存乏沸,會(huì)進(jìn)入segregated_band_grow方法申請(qǐng)開辟新的band。每個(gè)Band固定大小2M匙瘪,可容納16個(gè)128kb的槽蝶缀,如果當(dāng)前Band中的slot耗盡,會(huì)向系統(tǒng)申請(qǐng)新的band驻啤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荐吵,一起剝皮案震驚了整個(gè)濱河市赊瞬,隨后出現(xiàn)的幾起案子贼涩,更是在濱河造成了極大的恐慌,老刑警劉巖谤绳,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缩筛,死亡現(xiàn)場(chǎng)離奇詭異堡称,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)却紧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門晓殊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人认烁,你說我怎么就攤上這事识藤。” “怎么了痴昧?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵赶撰,是天一觀的道長。 經(jīng)常有香客問我餐胀,道長瘤载,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任墨技,我火速辦了婚禮,結(jié)果婚禮上扣汪,老公的妹妹穿的比我還像新娘崭别。我一直安慰自己,他們只是感情好茅主,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布暗膜。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪论衍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天炬丸,我揣著相機(jī)與錄音蜒蕾,去河邊找鬼。 笑死首启,一個(gè)胖子當(dāng)著我的面吹牛撤摸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钥飞,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼衫嵌,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了结闸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤幔亥,失蹤者是張志新(化名)和其女友劉穎察纯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體香伴,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡即纲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年博肋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膊畴。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡病游,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出买猖,到底是詐尸還是另有隱情滋尉,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布奸远,位于F島的核電站讽挟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏薛窥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一佩番、第九天 我趴在偏房一處隱蔽的房頂上張望罢杉。 院中可真熱鬧,春花似錦滩租、人聲如沸律想。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽而叼。三九已至,卻和暖如春澈歉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國打工涡尘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留响迂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓川梅,卻偏偏與公主長得像然遏,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子待侵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容