iOS开发底层之RuntimeObjc_msgSend探究下 - 09
Posted iOS_developer_zhong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发底层之RuntimeObjc_msgSend探究下 - 09相关的知识,希望对你有一定的参考价值。
一、上篇文章补充
1.GetClassFromIsa_p16 宏解读
- 上篇文章,没有解读此处, 源码如下。
.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
- 得到class后,接下来探索下CacheLookup缓存查找流程。
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
二、CacheLookup探索
- 源码探索,看看到底如何命中缓存,
.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类对象地址后,去进行缓存查找方法。 步骤如下:
- 对类对象地址,进行指针平移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
- 在真机环境下, mask和buckets共占用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
- cache_hash算法如下:
3.1 p1(_cmd)右移7位,进行异或运算, 然后给 p12 , 然后p11右移48位,得到mask ,然后mask与p12进行与操作获取下标 , 然后赋值给 p12 , 通过上面的操作,我们记录下相关运算后的各参数的结果。
- p11 = _bucketsAndMaybeMask
- p10 = buckets
- p12 = 经过运算后查找的方法的hash下标
- 得到了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的倍数位移)。
- 下面进入了一个循环查找过程, 进行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
- 如果上面的循环查找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
- 再次,开启了一个循环, 这个循环是从最后一个位置,往前进行查询, 首先重新设置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
- 命中缓存的方法 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
- 缓存找不到, lookUpImpOrForward - 慢速 - methodlsit
三、lookUpImpOrForward 探索
- 一起来看下这个方法里面是如何实现的, 上源码:
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的主要内容,如果未能解决你的问题,请参考以下文章