iOS开发底层之RuntimeObjc_msgSend探究下 - 09

Posted iOS_developer_zhong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之RuntimeObjc_msgSend探究下 - 09相关的知识,希望对你有一定的参考价值。


一、上篇文章补充

1.GetClassFromIsa_p16 宏解读

  1. 上篇文章,没有解读此处, 源码如下。
.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */

#if SUPPORT_INDEXED_ISA
	// Indexed isa
	mov	p16, \\src			// optimistically set dst = src
	tbz	p16, #ISA_INDEX_IS_NPI_BIT, 1f	// done if not non-pointer isa
	// isa in p16 is indexed
	adrp	x10, _objc_indexed_classes@PAGE
	add	x10, x10, _objc_indexed_classes@PAGEOFF
	ubfx	p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
	ldr	p16, [x10, p16, UXTP #PTRSHIFT]	// load class from array
1:

#elif __LP64__
.if \\needs_auth == 0 // _cache_getImp takes an authed class already
	mov	p16, \\src
.else
	// 64-bit packed isa
	ExtractISA p16, \\src, \\auth_address
.endif
#else
	// 32-bit raw isa
	mov	p16, \\src

#endif

.endmacro

上述代码中GetClassFromIsa_p16是定义的一个宏,主要功能为,通过isa找到对应的类;ExtractISA也是个宏定义,功能为将传入的isa&isaMask,得到class,并将class赋给p16

  1. 得到class后,接下来探索下CacheLookup缓存查找流程。
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

二、CacheLookup探索

  1. 源码探索,看看到底如何命中缓存,
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
	
	mov	x15, x16			// stash the original isa
LLookupStart\\Function:
	// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	ldr	p10, [x16, #CACHE]				// p10 = mask|buckets
	lsr	p11, p10, #48			// p11 = mask
	and	p10, p10, #0xffffffffffff	// p10 = buckets
	and	w12, w1, w11			// x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	ldr	p11, [x16, #CACHE]			// p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
	tbnz	p11, #0, LLookupPreopt\\Function
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
#else
	and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
	tbnz	p11, #0, LLookupPreopt\\Function
#endif
	eor	p12, p1, p1, LSR #7
	and	p12, p12, p11, LSR #48		// insert的时候也是右移7位,所以取一样的, x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
	and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	ldr	p11, [x16, #CACHE]				// p11 = mask|buckets
	and	p10, p11, #~0xf			// p10 = buckets
	and	p11, p11, #0xf			// p11 = maskShift
	mov	p12, #0xffff
	lsr	p11, p12, p11			// p11 = mask = 0xffff >> p11
	and	p12, p1, p11			// x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
     // 循环流程  p13,对应下标,第一个要查的bucket(sel - imp)
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

						// do {
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
	cmp	p9, p1				//     if (sel != _cmd) {
	b.ne	3f				//         scan more,不等于就去第三部
						//     } else {
2:	CacheHit \\Mode				// hit:    call or return imp,缓存命中
						//     }
3:	cbz	p9, \\MissLabelDynamic		//     if (sel == 0) goto Miss; // 没有找到
	cmp	p13, p10			// } while (bucket >= buckets)   循环条件, 是否超出了buckets内存地址
	b.hs	1b

	// wrap-around:
	//   p10 = first bucket
	//   p11 = mask (and maybe other bits on LP64)
	//   p12 = _cmd & mask

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	add	p13, p10, w11, UXTW #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						// p13 = buckets + (mask << 1+PTRSHIFT)
						// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	add	p13, p10, p11, LSL #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

						// do {
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
	cmp	p9, p1				//     if (sel == _cmd)
	b.eq	2b				//         goto hit
	cmp	p9, #0				// } while (sel != 0 &&
	ccmp	p13, p12, #0, ne		//     bucket > first_probed)
	b.hi	4b

上述流程,暂且只分析CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS环境。 重要步骤为 解释 +源码来描述。

接本篇第一大点,获取到class类对象地址后,去进行缓存查找方法。 步骤如下:

  1. 对类对象地址,进行指针平移16个字节,得到cache_t的首地址,这个可以看前面objc_class的内存结构成员变量表结构, 在cache_t之前,有 isa,还有superclass,所以这里平移16个字节。
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	ldr	p10, [x16, #CACHE]				// p10 = mask|buckets
	lsr	p11, p10, #48			// p11 = mask
  1. 在真机环境下, maskbuckets共占用64位,mask占高16位, buckets占低48位, 现在为了得到buckets,所以进行了掩码运算, 源码为and p10, p11, #0x0000ffffffffffff, 然后找到buckets后,就涉及到了 cache_hash算法,源码如下:
#if __has_feature(ptrauth_calls)
	tbnz	p11, #0, LLookupPreopt\\Function
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
#else
	and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
	tbnz	p11, #0, LLookupPreopt\\Function
#endif  
    // cache_hash算法
	eor	p12, p1, p1, LSR #7         // p1 = _cmd
	and	p12, p12, p11, LSR #48		// insert的时候也是右移7位,所以取一样的, x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
	and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
  1. cache_hash算法如下:
    3.1 p1(_cmd)右移7位,进行异或运算, 然后给 p12 , 然后p11右移48位,得到mask ,然后mask与p12进行与操作获取下标 , 然后赋值给 p12 , 通过上面的操作,我们记录下相关运算后的各参数的结果。
  • p11 = _bucketsAndMaybeMask
  • p10 = buckets
  • p12 = 经过运算后查找的方法的hash下标
  1. 得到了hash下标, 然后buckets的首地址也知道,现在就开始要寻找bucket
     // 循环流程  p13,对应下标,第一个要查的bucket(sel - imp)  PTRSHIFT = 3 
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

算法解读为 , PTRSHIFT = 3, 所以 相当于 对 p12进行左移16位, (sel 8位, imp8位), p10是buckets收地址,加上 p12(经过运算,相当于16的倍数位移)。

  1. 下面进入了一个循环查找过程, 进行sel对比, 直到超出了buckets范围。
						// do {
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
	cmp	p9, p1				//     if (sel != _cmd) {
	b.ne	3f				//         scan more,不等于就去第三部
						//     } else {
2:	CacheHit \\Mode				// hit:    call or return imp,缓存命中
						//     }
3:	cbz	p9, \\MissLabelDynamic		//     if (sel == 0) goto Miss; // 没有找到
	cmp	p13, p10			// } while (bucket >= buckets)   循环条件, 是否超出了buckets内存地址
	b.hs	1b
  1. 如果上面的循环查找sel,没有找到,就会走到下面的查找流程。 从最后mask的存储位置往前查找。
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	add	p13, p10, w11, UXTW #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						// p13 = buckets + (mask << 1+PTRSHIFT)
						// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	add	p13, p10, p11, LSL #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
  1. 再次,开启了一个循环, 这个循环是从最后一个位置,往前进行查询, 首先重新设置p12的下标。 然后开启查找循环。
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

						// do {
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
	cmp	p9, p1				//     if (sel == _cmd)
	b.eq	2b				//         goto hit
	cmp	p9, #0				// } while (sel != 0 &&
	ccmp	p13, p12, #0, ne		//     bucket > first_probed)
	b.hi	4b
// 上述循环,没有走完,就会走到 MissLabelDynamic流程
LLookupEnd\\Function:
LLookupRecover\\Function:
	b	\\MissLabelDynamic
  1. 命中缓存的方法 CacheHit探索
// 参数说明:CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
	TailCallCachedImp x17, x10, x1, x16	// authenticate and call imp
.elseif $0 == GETIMP
	mov	p0, p17
	cbz	p0, 9f			// don't ptrauth a nil imp
	AuthAndResignAsIMP x0, x10, x1, x16	// authenticate imp and re-sign as IMP
9:	ret				// return IMP
.elseif $0 == LOOKUP
	// No nil check for ptrauth: the caller would crash anyway when they
	// jump to a nil IMP. We don't care if that jump also fails ptrauth.
	AuthAndResignAsIMP x17, x10, x1, x16	// authenticate imp and re-sign as IMP
	cmp	x16, x15
	cinc	x16, x16, ne			// x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
	ret				// return imp via x17
.else
.abort oops
.endif
.endmacro
  1. 缓存找不到, lookUpImpOrForward - 慢速 - methodlsit

三、lookUpImpOrForward 探索

  1. 一起来看下这个方法里面是如何实现的, 上源码:
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
    runtimeLock.assertUnlocked();
    runtimeLock.lock();  //1.多线程保护,进行安全加锁
    checkIsKnownClass(cls); //2.判断当前类有没有进行注册
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);  //3. ro ,rw的处理, 
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;


    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

以上是关于iOS开发底层之RuntimeObjc_msgSend探究下 - 09的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发底层之RuntimeObjc_msgSend探究 - 08

iOS开发之结构体底层探索

iOS开发底层之对象的本质-04

iOS开发底层之内存对齐详解-03

iOS开发底层之NSObject-alloc源码分析-02

iOS开发底层之消息的快速与慢速转发 - 11