iOS:SideTable

打印 上一主题 下一主题

主题 855|帖子 855|积分 2565

本文源码来自于 objc4-756.2 版本;
  本文研究 sideTable 在 objc4 源码中的使用及其作用,从而剖析 iOS 中引用计数器和弱引用的实现原理;
1. retain 利用

我们都知道,新版本的 objc 中引入了 Tagged Pointer,且 isa 采用 union 的方式进行构造,此中 isa 的布局体中有一个 extra_rc 和 has_sidetable_rc,这两者共同记录引用计数器。
直接看看 objc_object::rootRetain() 方法,只看 extra_rc 超出之后 sidetable 相关的代码,删减之后如下:
  1. uintptr_t carry;
  2.     newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
  3. if (carry) {
  4.     // Leave half of the retain counts inline and prepare to copy the other half to the side table.
  5.     transcribeToSideTable = true;
  6.     newisa.extra_rc = RC_HALF;
  7.     newisa.has_sidetable_rc = true;
  8. }
  9. if (slowpath(transcribeToSideTable)) {
  10.         // Copy the other half of the retain counts to the side table.
  11.         sidetable_addExtraRC_nolock(RC_HALF);
  12. }
复制代码
那么关键方法就是 sidetable_addExtraRC_nolock():
  1. bool
  2. objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
  3. {
  4.     assert(isa.nonpointer);
  5.     // 取出this对象所在的SideTable
  6.     SideTable& table = SideTables()[this];
  7.     // 取出SideTable中存储的refcnts,类型为Map
  8.     size_t& refcntStorage = table.refcnts[this];
  9.     // 记录原始的引用计数器
  10.     size_t oldRefcnt = refcntStorage;
  11.     // 容错处理
  12.     assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
  13.     assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
  14.     if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
  15.     uintptr_t carry;
  16.     size_t newRefcnt =
  17.         addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
  18.     if (carry) {
  19.         // SideTable溢出处理
  20.         refcntStorage =
  21.             SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
  22.         return true;
  23.     } else {
  24.         // SideTable未溢出
  25.         refcntStorage = newRefcnt;
  26.         return false;
  27.     }
  28. }
复制代码
这个函数的逻辑如下:

  • 根据 this,也就是对象的地址从 SideTables 中取出一个 SideTable;
  • 获取 SideTable 的 refcnts,这个成员变量是一个 Map;
  • 存储旧的引用计数器;
  • 进行 add 盘算,并记录是否有溢出;
  • 根据是否溢出盘算并记录结果,最后返回;
那么,这里有几个点需要解开:

  • 什么是 SideTables;
  • 什么是 SideTable;
  • 什么是 refcnts;
  • add 的盘算逻辑为什么需要位移?
  • SideTable 中的溢出时如何处置处罚的?
接下来,一一办理~~~
2. SideTables

直接来看 SideTables 的代码:
  1. static StripedMap<SideTable>& SideTables() {
  2.     return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
  3. }
复制代码
起首,这是个静态函数,返回 StripedMap<SideTable> 类型,但是 & 是什么意思呢?这个是 C++ 语法,表示返回引用类型,看个例子:
         
          &的用法    & 的用法还有些限定,比如不能返回栈中的引用,否则会栈变量消失后会出现 error,还有一些其他的限定,有兴趣可以穷究,这里只需要知道 & 表示返回引用类型,也就是可以通过 & func() 来获取函数返回值的指针,其他的不再赘述;
接着,比较懵逼的是 *reinterpret_cast ,其实这个是 C++ 的逼迫类型转换语法,不用穷究,有兴趣的可以自行百度。
以是,总结下这段代码:

  • SideTables() 使用 static 修饰,是一个静态函数;
  • & 表示返回引用类型;
  • reinterpret_cast 是一个逼迫类型转换符号;
  • 函数最终的结果就是返回 SideTableBuf;
那么 SideTableBuf 又是什么?
3. SideTableBuf

直接看代码:
  1. // We cannot use a C++ static initializer to initialize SideTables because
  2. // libc calls us before our C++ initializers run. We also don't want a global
  3. // pointer to this struct because of the extra indirection.
  4. // Do it the hard way.
  5. alignas(StripedMap<SideTable>) static uint8_t
  6.     SideTableBuf[sizeof(StripedMap<SideTable>)];
复制代码
起首看解释,分析白两点:

  • SideTables 在 C++ 的 initializers 函数之前被调用,以是不能使用 C++ 初始化函数来初始化 SideTables,而 SideTables 本质就是 SideTableBuf;
  • 不能使用全局指针来指向这个布局体,因为涉及到重定向问题;
   其实照旧比较懵逼为什么 SideTableBuf 要这么设计,原理有待讲求~~~估计和初始化有关;
  继续看 SideTableBuf,要点包括:

  • alignas 表示对齐;
  • StripedMap<SideTable> 的 size 为 4096(存疑,待验证);
  • uint8_t 现实上是 unsigned char 类型,即占 1 个字节;
由此可以得出:


  • SideTableBuf 本质上是一个长度为 sizeof(StripedMap<SideTable>) 的 char 类型的数组;
同时也可以这么明白:


  • SideTableBuf 本质上就是一个巨细为和 StripedMap<SideTable> 对象同等的内存块;
这也是为什么 SideTableBuf 可以用来表示 StripedMap<SideTable> 对象。本质上而言,SideTableBuf 就是指一个 StripedMap<SideTable>对象;
那么接下来就是搞清楚 StripedMap<SideTable> 是个什么东西了......
4. StripedMap<SideTable>

先上代码,删减一些方法之后的代码为:
  1. template<typename T>
  2. class StripedMap {
  3. #if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
  4.     enum { StripeCount = 8 };
  5. #else
  6.     enum { StripeCount = 64 };
  7. #endif
  8.     struct PaddedT {
  9.         T value alignas(CacheLineSize);
  10.     };
  11.     PaddedT array[StripeCount];
  12.     static unsigned int indexForPointer(const void *p) {
  13.         uintptr_t addr = reinterpret_cast<uintptr_t>(p);
  14.         return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
  15.     }
  16. public:
  17.     T& operator[] (const void *p) {
  18.         return array[indexForPointer(p)].value;
  19.     }
  20.     const T& operator[] (const void *p) const {
  21.         return const_cast<StripedMap<T>>(this)[p];
  22.     }
  23.     ...省略了对象方法...
  24. }
复制代码
上述代码的逻辑为:

  • 根据是否为 iphone 定义了一个 StripeCount,iphone 下为 8;
  • 源码中 CacheLineSize 为 64,使用 T 定义了一个布局体,而 T 就是 SideTable 类型;
  • 天生了一个长度为 8 类型为 SideTable 的数组;
  • indexForPointer() 逻辑为根据传入的指针,经过一定的算法,盘算出一个存储该指针的位置,因为使用了取模运算,以是值为 0 - StripeCount;
  • 背面的 operator 表示重写了运算符 [] 的逻辑,调用了 indexForPointer() 方法,这样使用起来更像一个数组;
至此,SideTables 的寄义已经很清楚了:


  • SideTables 可以明白成一个类型为 StripedMap<SideTable> 静态全局对象,内部以数组的形式存储了 StripeCount 个 SideTable;
那么第一个问题已包办理,按照 sidetable_addExtraRC_nolock() 方法中的逻辑,先从 SideTables 数组中取出一个 SideTable,然后进行相关利用,以是现在就来看看 SideTable 是个啥~~~
5. SideTable

  1. struct SideTable {
  2.     spinlock_t slock;
  3.     RefcountMap refcnts;
  4.     weak_table_t weak_table;
  5.     SideTable() {
  6.         memset(&weak_table, 0, sizeof(weak_table));
  7.     }
  8.     ~SideTable() {
  9.         _objc_fatal("Do not delete SideTable.");
  10.     }
  11.     ...省略对象方法...
  12. }
复制代码
可以看到,SideTable 有三个成员变量:

  • spinlock_t:自旋锁,负责加锁相关逻辑;
  • refcnts:存储引用计数器的 Map;
  • weak_table:存储弱引用的表;
自旋锁暂不讨论,来看看 refcnts 的定义:
  1. typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;
复制代码
DenseMap 过于复杂,我们看看基类 DenseMapBase 中的部门代码,如下,DenseMapBase中重写了利用符 []:
  1. ValueT &operator[](const KeyT &Key) {
  2.     return FindAndConstruct(Key).second;
  3.   }
复制代码
大意是通过传入的 Key 寻找对应的 Value。而 Key 是 DisguisedPtr<objc_object> 类型,Value 是 size_t 类型。
回到最初的 sidetable_addExtraRC_nolock 方法中:
  1. size_t& refcntStorage = table.refcnts[this];
复制代码
上述代码就是通过 this ,即 object 对象的地址,取出 refcnts 这个哈希表中存储的引用计数器;
refcnts 可以明白成一个 Map,使用 address:refcount 的形式存储了许多个对象的引用计数器;
6. 总结

         
          SideTables 和 SideTable   

  • iphone 中 SideTables() 本质是返回一个 SideTableBuf 对象,该对象存储 8 个 SideTable;
  • 因为涉及到多线程和服从的问题,肯定不可能只使用一个 SideTable 来存储对象相关的引用计数器和弱引用;
  • Apple 通过对 object 的地址进行运算之后,对 SideTable 的个数进行取模运算,以此来决定将对象分配到哪个 SideTable 进行信息存储,因为有取模运算,不会出现数组溢出的环境;
总结:


  • objc 中当对象需要使用到 sideTable 时,会被分配到 8/64 个全局 sideTables 中的某一个表中存储相关的引用计数器或者弱引用信息;
7. weak_table

继续看弱引用如何实现的,看看 weak_table_t 源码:
  1. /**
  2. * The global weak references table. Stores object ids as keys,
  3. * and weak_entry_t structs as their values.
  4. */
  5. struct weak_table_t {
  6.     weak_entry_t *weak_entries;
  7.     size_t    num_entries;
  8.     uintptr_t mask;
  9.     uintptr_t max_hash_displacement;
  10. };
复制代码
解释也分析白 weak_table_t 是一个全局引用表,object 的地址作为 key,weak_entry_t 作为 Value。只不过这个全局引用表有 8 或者 64 个;
那么这个 weak_entry_t 是什么,又是怎么用的呢,弱引用的存储逻辑是怎样的?
         
          image.png    疑问


  • 为什么 weak 能够自动置位 nil?
  • 析构函数?
SideTables 的初始化时机和流程

isa 中的 shiftcls 溢出时则调用 sidetable_addExtraRC_nolock 方法将一半的引用计数器存入 sidetable 中
从上面的代码来看,逻辑相对清楚:

  • SideTables 应该是一个全局性的东西;
  • 传入 this 取出 当前对象相关的 SideTable;
  • SideTable 有一个 refcnts 的 map,仍然根据 this 取出这个旧的引用计数器;
  • 通过 addc 盘算,需要位移估计是因为 refcnts 在 sideTable 中的位置有关吧;
  • 盘算完毕,如果没有溢出,则直接新值替换旧值;
  • 如果发生溢出,则??
接下来 SideTable:
  1. struct SideTable {
  2.     spinlock_t slock;
  3.     RefcountMap refcnts;
  4.     weak_table_t weak_table;
  5.     ...省略对象方法...
  6. }
复制代码
SideTables 应该是一个全局变量,其定义如下:
  1. static StripedMap<SideTable>& SideTables() {
  2.     return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
  3. }
复制代码
如上,是一个静态方法,获取 StripedMap<SideTable> 类型的变量的
SideTables 的初始化

         
          image.png            
          image.png            
          image.png            
          image.png            
          image.png    notifyBatchPartial 方法中,state = dyld_image_state_bound 时:
  1. (*sNotifyObjCMapped)(objcImageCount, paths, mhs);
复制代码
         
          image.png            
          image.png            
          image.png
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

半亩花草

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表