阅读 426

CoreFoundation CFRuntimeBase下的_cfinfo[4]存储信息探究

前言

纯属探究,不对的地方请指出

为什么要探究这个,在学习runloop的过程中发现了一些,很难看的东西,在cf下可以看到充斥着各种各样的类似的函数


/* Bit 3 in the base reserved bits is used for invalid state in run loop objects */
CF_INLINE void __CFSetValid(void *cf) {
    __CFBitfieldSetValue(((CFRuntimeBase *)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 1);
}

CF_INLINE void __CFUnsetValid(void *cf) {
    __CFBitfieldSetValue(((CFRuntimeBase *)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 0);
}

复制代码

注释上已经很明确了,这个位代表着什么状态,于是乎我就猜想,是不是这个_cfinfo下面都有着通用的位来表示状态,就因此查看代码有了如下的结论。(咱们就不卖关子了啊,直接把结论写上,之后再说怎么看的)

_cfinfo[4]存储了什么信息

typedef struct __CFRuntimeBase {
    uintptr_t _cfisa;
    uint8_t _cfinfo[4];
#if __LP64__
    uint32_t _rc;
#endif
} CFRuntimeBase;
复制代码

isa指针不用多说,是个iOS开发都知道。

cfinfo[4]是个数组,类型是uint8_t,一个字节,跟char是一个样子的,能存储8位信息;4*8也就可以存储32位信息。

下面是结论:(不对请指出,但是不要喷啊,小编头一次发帖)

1-8位:

第8位:是否是系统默认分配的空间。kCFAllocatorSystemDefault

1~7位存储的信息都是不同的,看每个cf对象下的类型。

eg:(CFRunloopObserver,第一位firing,第二位repeats)

/* Bit 0 of the base reserved bits is used for firing state */
/* Bit 1 of the base reserved bits is used for repeats state */

CF_INLINE Boolean __CFRunLoopObserverIsFiring(CFRunLoopObserverRef rlo) {
    return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 0, 0);
}

CF_INLINE void __CFRunLoopObserverSetFiring(CFRunLoopObserverRef rlo) {
    __CFBitfieldSetValue(((CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 0, 0, 1);
}

CF_INLINE void __CFRunLoopObserverUnsetFiring(CFRunLoopObserverRef rlo) {
    __CFBitfieldSetValue(((CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 0, 0, 0);
}

CF_INLINE Boolean __CFRunLoopObserverRepeats(CFRunLoopObserverRef rlo) {
    return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 1, 1);
}

CF_INLINE void __CFRunLoopObserverSetRepeats(CFRunLoopObserverRef rlo) {
    __CFBitfieldSetValue(((CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 1, 1, 1);
}

CF_INLINE void __CFRunLoopObserverUnsetRepeats(CFRunLoopObserverRef rlo) {
    __CFBitfieldSetValue(((CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 1, 1, 0);
}
复制代码

还有其中array是否是mutiable的在第七位表示,在__CFArrayInit可以看到,不过得自己计算,就不贴代码了。可以推想一下dict或者其他的collect的mutiable是否在第七位存储。

9-18位:CFTypeID

19-21位不详,快找瞎了ಥ_ಥ,求哪位大神看到可以补充一下

22位:deallocing

23位:dealloced

24位:是否是自定义引用计数的对象

25-32:引用计数

还可以看到在64位下,用_rc代表着引用计数,至于上面25-32位在64位用来干啥,暂时没看到,希望大神指出。

#if __LP64__
    uint32_t _rc;
#endif
复制代码

关键宏以及一些注意点

CFRuntime.h

#if __BIG_ENDIAN__
#define INIT_CFRUNTIME_BASE(...) {0, {0, 0, 0, 0x80}}
#else
#define INIT_CFRUNTIME_BASE(...) {0, {0x80, 0, 0, 0}}
#endif
复制代码

CFInternal.h

#if defined(__BIG_ENDIAN__)
#define __CF_BIG_ENDIAN__ 1
#define __CF_LITTLE_ENDIAN__ 0
#endif

#if defined(__LITTLE_ENDIAN__)
#define __CF_LITTLE_ENDIAN__ 1
#define __CF_BIG_ENDIAN__ 0
#endif
复制代码
#define CF_INFO_BITS (!!(__CF_BIG_ENDIAN__) * 3)
#define CF_RC_BITS (!!(__CF_LITTLE_ENDIAN__) * 3)
复制代码
#define __CFBitfieldMask(N1, N2)	((((UInt32)~0UL) << (31UL - (N1) + (N2))) >> (31UL - N1))
#define __CFBitfieldGetValue(V, N1, N2)	(((V) & __CFBitfieldMask(N1, N2)) >> (N2))
#define __CFBitfieldSetValue(V, N1, N2, X)	((V) = ((V) & ~__CFBitfieldMask(N1, N2)) | (((X) << (N2)) & __CFBitfieldMask(N1, N2)))
#define __CFBitfieldMaxValue(N1, N2)	__CFBitfieldGetValue(0xFFFFFFFFUL, (N1), (N2))
复制代码

BIG_ENDIAN 大端模式 高字节的存储在内存的低低地址

LITTLE_ENDIAN 小端模式 反之

具体的什么自行百度,直接举个例子 0x01 23 45 67

大端 67 45 23 01

小端 01 23 45 67

这个跟cpu有关,我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

所以CF_INFO_BITS在小端代表的就是0

 __CFBitfieldSetValue(((CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 0, 0, 0)
 等价于
  __CFBitfieldSetValue(((CFRuntimeBase *)rlo)->_cfinfo[0], 0, 0, 0)
复制代码

__CFBitfieldSetValue(V, N1, N2, X)是个啥玩意?

我们存储的是位信息,你也可以用10进制来对cfinfo直接存储,但是不是太烧脑。所以通过移位来表示找到我们想要标记的位。

x来表示是不是对这个位置标记是1还是0,通过左移N2位之后同或之后的位为0。

至于N1可以先不用管它。N2就是表示你要标记为1的位。

说了这么多,还是自己推算一下才可以理解这个是怎么用的。

在哪里看到的

前面我bb了这么半天,你在哪看到的,你是不是在那忽悠我呢?

image

CFRuntime.c -> _CFRetain 函数这里基本上涵盖了全部,具体看注释部分

static CFTypeRef _CFRetain(CFTypeRef cf, Boolean tryR) {
    uint32_t cfinfo = *(uint32_t *)&(((CFRuntimeBase *)cf)->_cfinfo);
    if (cfinfo & 0x800000) { // '自定义引用计数'
        if (tryR) return NULL;
        CFTypeID typeID = (cfinfo >> 8) & 0x03FF; // mask up to 0x0FFF
        CFRuntimeClass *cfClass = __CFRuntimeClassTable[typeID];
        uint32_t (*refcount)(intptr_t, CFTypeRef) = cfClass->refcount;
        if (!refcount || !(cfClass->version & _kCFRuntimeCustomRefCount) || (((CFRuntimeBase *)cf)->_cfinfo[CF_RC_BITS] != 0xFF)) {
            HALT; // bogus object
        }
#if __LP64__
        if (((CFRuntimeBase *)cf)->_rc != 0xFFFFFFFFU) {
            HALT; // bogus object
        }
#endif
        refcount(+1, cf);
        return cf;
    }

    Boolean didAuto = false;
    if (tryR && (cfinfo & (0x400000 | 0x200000))) return NULL; //' deallocating or deallocated'
#if __LP64__
    if (0 == ((CFRuntimeBase *)cf)->_rc && !CF_IS_COLLECTABLE(cf)) return cf;	// Constant CFTypeRef
    uint32_t lowBits;
    do {
	lowBits = ((CFRuntimeBase *)cf)->_rc;
    } while (!CAS32(lowBits, lowBits + 1, (int32_t *)&((CFRuntimeBase *)cf)->_rc));
    // GC:  0 --> 1 transition? then add a GC retain count, to root the object. well remove it on the 1 --> 0 transition.
    if (lowBits == 0 && CF_IS_COLLECTABLE(cf)) {
	auto_zone_retain(objc_collectableZone(), (void*)cf);
	didAuto = true;
    }
#else
#define RC_START 24
#define RC_END 31
    CFIndex rcLowBits = __CFBitfieldGetValue(cfinfo, RC_END, RC_START);
    if (__builtin_expect(0 == rcLowBits, 0) && !CF_IS_COLLECTABLE(cf)) return cf;	// Constant CFTypeRef
    volatile uint32_t *infoLocation = (uint32_t *)&(((CFRuntimeBase *)cf)->_cfinfo);
    bool success = 0;
    do {
        cfinfo = *infoLocation;
        uint32_t prospectiveNewInfo = cfinfo; // dont want compiler to generate prospectiveNewInfo = *infoLocation.  This is why infoLocation is declared as a pointer to volatile memory.
        prospectiveNewInfo += (1 << RC_START);
        rcLowBits = __CFBitfieldGetValue(prospectiveNewInfo, RC_END, RC_START);
        if (__builtin_expect((rcLowBits & 0x7f) == 0, 0)) {
            /* '
             将另一位滚动到外部引用计数
             实际参考计数=低7位信息[CF_RC_BITS] +外部参考计数<< 6
             低位的第8位表示外部引用计数正在使用中。
             外部参考计数移位6而不是7,这样我们可以将低位设置为1100 0000而不是1000 0000。
             这样可以防止当复合保留计数恰好在1 << 7的倍数附近时,需要访问连续保留和释放的外部引用计数.'
             */
            prospectiveNewInfo = cfinfo;
            __CFBitfieldSetValue(prospectiveNewInfo, RC_END, RC_START, ((1 << 7) | (1 << 6)));
            __CFLock(&__CFRuntimeExternRefCountTableLock);
            success = CAS32(*(int32_t *)& cfinfo, *(int32_t *)&prospectiveNewInfo, (int32_t *)infoLocation);
            if (__builtin_expect(success, 1)) {
                __CFDoExternRefOperation(350, (id)cf);
            }
            __CFUnlock(&__CFRuntimeExternRefCountTableLock);
        } else {
            success = CAS32(*(int32_t *)& cfinfo, *(int32_t *)&prospectiveNewInfo, (int32_t *)infoLocation);
            // XXX_PCB:  0 --> 1 transition? then add a GC retain count, to root the object. we ll remove it on the 1 --> 0 transition.
            if (success && __CFBitfieldGetValue(cfinfo, RC_END, RC_START) == 0 && CF_IS_COLLECTABLE(cf)) {
		auto_zone_retain(objc_collectableZone(), (void*)cf);
		didAuto = true;
	    }
        }
    } while (__builtin_expect(!success, 0));
#endif
    if (!didAuto && __builtin_expect(__CFOASafe, 0)) {
	__CFRecordAllocationEvent(__kCFRetainEvent, (void *)cf, 0, CFGetRetainCount(cf), NULL);
    }
    return cf;
}
复制代码

结语

对于这个帖子其实可有可无,不过我个人感觉理解了这些,至少在看CF框架的降低了一些难度,毕竟关于CF的帖子很少。这个帖子只是关于自己的一些问题的记录,其中肯定有不对的,还请小伙伴指正。

思考 & 疑问

1.cfinfo为什么这么写,而不是直接用一个32位表示?自己的猜测,可能跟历史原因,因为远古的时候地址总线是16位的有关系。

2.但出现了64位的cpu之后24-32位存储引用计数为什么不用了,而用了_rc来存储,难道是用来干其他的事么

3.19-21位这个还没找到对应的信息。。

4.iOS的64位下的isa的指针,除了内存地址另外的32位是否也是同样信息?

文章分类
iOS
文章标签