iOS底层原理:消息转发之快速/缓存查找

2020-09-19  本文已影响0人  打碟的DJ

在上篇博客iOS底层原理:cache_t分析中已经分析了cache的存储方法,那么如何去查找呢?
则就是我们这次的重点了~~~

Runtime

首先在开始分析如何查找cache的时候,我们先介绍下,什么是编译时运行时

编译时

将源代码翻译成机器能识别的代码。

主要是进行了词法分析和语法分析;主要是进行类型检查,初步扫描,此时代码还没放到内存中运行起来。常见的就是我们build完毕之后的errorwarning都是编译器检查出来的。

运行时

代码运行起来,被装载到内存中

运行时类型检查是在内存中做了些操作,判断是否符合逻辑规范

Runtime 被调用的三种途径

架构 三种方式

三种方式,在经过编译器处理后,最后都会调用Runtime中的API方法。

Clang 了解底层

main函数代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person alloc];

        [person say1];
        [person say2];
        [person say3];
    }
    return 0;
}

clang编译后源码:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));

        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("say1"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("say2"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("say3"));
    }
    return 0;
}

从上面的对比中,我们其实可以看到,所有的方法调用其实都是通过调用objc_msgSend的。

顾名思义,在iOS中所有的方法,其实就是消息转发,而消息转发包含消息的接受者消息主体消息主体其实包含方法编号参数

id objc_msgSend(id self, SEL _cmd, ...);

所以,当我们调用方法的时候,其实就是调用了objc_msgSend(id self, SEL _cmd, ...),其实就是通过sel找到对应的imp(函数指针),imp指向了函数实现。

所以接下来我们着重分析一下objc_msgSend,也就是通过sel找到imp

objc_msgSend

通过源码,其实我们可以发现objc_msgSend其实是通过汇编来实现的。为什么要用汇编来实现呢?

好处

其实objc_msgSend大概流程是通过对象ISA找到方法(类),在类(objc_class)中找到cache,如果存在则调用,不存在则找methodlist(整个继承链去查找)。

objc781_objc_msgSend

通过整个流程图,我们去分析下汇编源码:

开始之前了解下部分汇编指令:

b.le :判断上面cmp的值是小于等于 执行标号,否则直接往下走
b.eq 等于 执行地址 否则往下
cmp 比较(Compare,比较两个数并且更新标志)
ldr 从存储器中加载(Load)字到一个寄存器(Register)中
mov 寄存器加载数据,既能用于寄存器间的传输,也能用于加载立即数(mov x0,#0x10 x0 = 0x10)

_objc_msgSend 源码分析

    ENTRY _objc_msgSend  // _objc_msgSend的入口函数
    UNWIND _objc_msgSend, NoFrame

    // 判断消息接受者是否为空
    cmp p0, #0          // nil check and tagged pointer check
    // 判断是否为taggedpinter对象
#if SUPPORT_TAGGED_POINTERS
    // 如果 cmp p0, #0 小于等于0,则执行标号 LNilOrTagged,否则直接往下走
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    // 等于 则执行标号 LReturnZero,否则往下走
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa 找到isa指针
    GetClassFromIsa_p16 p13     // p16 = class 获取class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    // 开始缓存查找
    CacheLookup NORMAL, _objc_msgSend
1、GetClassFromIsa_p16源码分析
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
    ...省略部分信息...
#elif __LP64__
    // 64-bit packed isa
    and p16, $0, #ISA_MASK
#else
    ...省略部分信息...
#endif
.endmacro

找到class之后,LGetIsaDoneisaclass已经完成了,开始进入缓存查找CacheLookup 入参NORMAL

2、CacheLookup源码分析
.macro CacheLookup

LLookupStart$1:

    // p1 = SEL, p16 = isa
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets  isa 指针偏移#CACHE(16位)得到cache的地址,也就是_maskAndBuckets

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets   p10 = p11 & 0x0000ffffffffffff,也就是将mask抹零,获取到buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask / p12 = p1 & (_maskAndBuckets >> 48),也就是 _cmd & mask,存入时候的hash算法
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    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


    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
                     // #define PTRSHIFT 3 ,也就是 p12 = buckets + ((_cmd & mask) << 4 )

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // (mask|bucket >> 44)  =  mask|bucket >> 48 << 4 = mask << 4
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p12, p12, p11, LSL #(1+PTRSHIFT)
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
    JumpMiss $0

.endmacro

在源码中已经加了部分注释,接下来我们对缓存查找这一步进行详细的分析:

根据cache::insert函数的分析,我们以最简单的情况来分析,mask_t m = capacity - 1;,也就是此时的mask = 3。所以 p12 = buckets + (0011 >> 4),也就是p12 = buckets + 48,此时p12就是我们buckets集合中的最后一个bucket

以上就是我们整个缓存方法的查找流程了。

3、JumpMiss 源码分析

.macro JumpMiss
.if $0 == GETIMP
    b   LGetImpMiss
.elseif $0 == NORMAL
    b   __objc_msgSend_uncached
.elseif $0 == LOOKUP
    b   __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

因为传入的$0NORMAL,所以我们直接看__objc_msgSend_uncached方法

__objc_msgSend_uncached 源码分析

    END_ENTRY __objc_msgSend_uncached


    STATIC_ENTRY __objc_msgLookup_uncached
    UNWIND __objc_msgLookup_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup
    ret

    END_ENTRY __objc_msgLookup_uncached

可以看到其实最后直接走了MethodTableLookup方法,直接探索下MethodTableLookup

MethodTableLookup

.macro MethodTableLookup
    
    // push frame
    ...省略部分代码...

    // save parameter registers: x0..x8, q0..q7
    ...省略部分代码...

    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    bl  _lookUpImpOrForward

    // IMP in x0
    mov x17, x0
    
    // restore registers and return

    mov sp, fp
    ldp fp, lr, [sp], #16
    AuthenticateLR

.endmacro

一顿疯狂的汇编代码,看的懵逼,直接找到主要方法_lookUpImpOrForward。当我们想继续探索的时候,发现在当前文件中已经搜索不到了。

其实到这里的时候,汇编的快速查找流程才是真正的结束了。接下来就进入了慢速查找流程。

上一篇 下一篇

猜你喜欢

热点阅读