探究runtime碰到的类和结构体

在研究 runtime 时能遇到许多的类和结构体, 因为不可能每篇文章碰到就介绍一遍, 所以在这里统一把这些碰到的类和结构体介绍一下

SideTable

这个类的作用是存放引用计数表和弱引用表, 并使用自旋锁来防止操作表结构时可能的竞态条件
定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;

SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}

~SideTable() {
_objc_fatal("Do not delete SideTable.");
}

void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }

// Address-ordered lock discipline for a pair of side tables.

template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

有下面几个成员变量

  • spinlock_t slock: 自旋锁

  • RefcountMap refcnts: 额外引用计数存储表

  • weak_table_t weak_table: 弱引用表, 用来存储弱引用者(weak 修饰的变量)

    其中RefcountMap是类型objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable>的别名

DisguisedPtr

定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
template <typename T>
class DisguisedPtr {
uintptr_t value;

static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}

static T* undisguise(uintptr_t val) {
return (T*)-val;
}

public:
DisguisedPtr() { }
DisguisedPtr(T* ptr)
: value(disguise(ptr)) { }
DisguisedPtr(const DisguisedPtr<T>& ptr)
: value(ptr.value) { }

DisguisedPtr<T>& operator = (T* rhs) {
value = disguise(rhs);
return *this;
}
DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
value = rhs.value;
return *this;
}

operator T* () const {
return undisguise(value);
}
T* operator -> () const {
return undisguise(value);
}
T& operator * () const {
return *undisguise(value);
}
T& operator [] (size_t i) const {
return undisguise(value)[i];
}

// pointer arithmetic operators omitted
// because we don't currently use them anywhere
};

这个类的作用是伪装指针, 避免引用计数表和弱引用表里面保存的指针被内存泄漏工具leaks当做是内存泄漏.

  • 它的成员变量value存储的是经过函数disguise()处理后的指针.
  • 你可以像操作指针一样操作DisguisedPtr实例. 为了实现这个功能, DisguisedPtr重载了许多的操作符, 例如->, *, =, ==, []. 不了解的同学可以自己查资料, 关键字 运算符重载operator

DisguisedPtr一般用来充当 Key, 保存在结构体中

RefcountMapValuePurgeable

定义如下:

1
2
3
4
5
struct RefcountMapValuePurgeable {
static inline bool isPurgeable(size_t x) {
return x == 0;
}
};

只有一个内联函数RefcountMapValuePurgeable, 用来判断这个值需要是否析构释放内存

DenseMapInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T>
struct DenseMapInfo<DisguisedPtr<T>> {
static inline DisguisedPtr<T> getEmptyKey() {
return DisguisedPtr<T>((T*)(uintptr_t)-1);
}
static inline DisguisedPtr<T> getTombstoneKey() {
return DisguisedPtr<T>((T*)(uintptr_t)-2);
}
static unsigned getHashValue(const T *PtrVal) {
return ptr_hash((uintptr_t)PtrVal);
}
static bool isEqual(const DisguisedPtr<T> &LHS, const DisguisedPtr<T> &RHS) {
return LHS == RHS;
}
};

它有两个内联函数:

  • getEmptyKey(): 生成 empty 的标记
  • getTombstoneKey(): 生成墓碑标记(即代表之前被人使用过但现在已经没人用了)

有两个静态函数:

  • getHashValue(): 根据指针返回一个哈希值
  • isEqual(): 判断两个参数是否相等

DenseMapInfo常常跟DisguisedPtr一起使用. 它的两个内联函数用来标记存储数据的DenseMapPair实例 bucket 的状态是否是 empty 或者 tombstoneKey. 它的getHashValue函数用来生成一个哈希值来确定 bucket 的序号

detail::DenseMapPair

部分定义如下:

1
2
3
4
5
6
7
8
9
10
template <typename KeyT, typename ValueT>
struct DenseMapPair : public std::pair<KeyT, ValueT> {

// FIXME: Switch to inheriting constructors when we drop support for older
// clang versions.
// NOTE: This default constructor is declared with '{}' rather than
// '= default' to work around a separate bug in clang-3.8. This can
// also go when we switch to inheriting constructors.
DenseMapPair() {}
}

它的父类std::pair是一个结构体模板, 它将两个数据组合成一个数据, 类似于我们经常用的字典.
DenseMapPair被用来在引用计数表中保存引用计数. 其中 key 的类型为DisguisedPtr

DenseMap

部分定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename KeyT, typename ValueT,
typename ValueInfoT = DenseMapValueInfo<ValueT>,
typename KeyInfoT = DenseMapInfo<KeyT>,
typename BucketT = detail::DenseMapPair<KeyT, ValueT>>
class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>,
KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT> {
friend class DenseMapBase<DenseMap, KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>;

// Lift some types from the dependent base class into this class for
// simplicity of referring to them.
using BaseT = DenseMapBase<DenseMap, KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>;

BucketT *Buckets;
unsigned NumEntries;
unsigned NumTombstones;
unsigned NumBuckets;

public:
/// Create a DenseMap wth an optional \p InitialReserve that guarantee that
/// this number of elements can be inserted in the map without grow()
explicit DenseMap(unsigned InitialReserve = 0) { init(InitialReserve); }
}

这个类就是之前提到的引用计数表, 它的成员里面有一个存储数组, 用来保存引用计数. 数组的元素类型为 detail::DenseMapPair
如果一个对象的引用计数曾经溢出保存到表中, 当对象被销毁时, 会将表中对象使用过的存储器 bucket 标记为墓碑状态

1
friend class DenseMapBase<DenseMap, KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>;

用于声明一个友元类, 这样DenseMapBase就能访问DenseMap里面的私有属性和私有方法了

1
using BaseT = DenseMapBase<DenseMap, KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>;

DenseMapBase<DenseMap, KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>添加一个类型别名

1
explicit DenseMap(unsigned InitialReserve = 0) { init(InitialReserve); }

上面那行代码的作用是显示的声明一个构造方式, 这样这类就不能隐式转换了

成员变量如下:

  • Buckets: 一个 bucket 数组, 用于保存数据. 可扩容
  • NumEntries: Buckets 数组中已经被使用的数目
  • NumTombstones: Buckets 数组中 tombstone 的数目
  • NumBuckets: Buckets 数组的数目

weak_table_t

1
2
3
4
5
6
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};

这个结构体用来存储弱引用条目, 弱引用条目里面保存着对象以及它的弱引用者们.
当数组数量超过 1024 且被使用的数量占比小于 1/16 时, 数组长度会缩小为原来的 1/8

它有 4 个成员变量:

  • weak_entry_t *weak_entries: weak_entry_t 类型的数组. 弱引用条目, 用来保存弱引用者(被 weak 修饰的指针)
  • size_t num_entries: 已经被使用的条目数量
  • uintptr_t mask: 条目数组的数量
  • uintptr_t max_hash_displacement: 用来记录数组中被使用条目的 index 的最大值

weak_entry_t

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};

bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}

weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};

这个结构体用来保存引用对象以及它的弱引用者, 属于一对多的关系
当弱引用比较少的时候会将弱引用者保存在结构体里面, 当弱引用者数量超过 4 时会保存到外部的数组中

它有两个成员变量, 一个是DisguisedPtr<objc_object> referent;, 另一个是联合体 union.
referent 表示引用对象, 当一个弱引用者引用了一个新的对象, 那么我们需要从弱引用条目中(weak_entry_t)移除该弱引用者

weak_referrer_tDisguisedPtr<objc_object *> 类型的别名

第二个成员变量 union 里面有两个结构体, 我这里称呼它们为 s1, s2.
因为是 union, 所以 s1 里面的 out_of_line_ness 跟 s2 里面的 inline_referrers 的第二个元素的低 2 个 bit 是重合的. 数组 inline_referrers 的元素是 weak_referrer_t 类型. 在 arm64 架构下, 指针 8 字节对齐, 意味着指针低 3 位肯定都是 0, 经过DisguisedPtr的伪装后, 它的低 2 位都是 1, 也就是 s1 的 out_of_line_ness 的值为 0b11.
这个特性用来标记是否使用内部数组来保存弱引用者, 当使用外部数组时, 内部数组被清空, 将 out_of_line_ness 赋值为 0b10 来表示使用外部数组

当使用内部数组时

  • s1 的成员变量均没有意义
  • 使用 s2 的 inline_referrers 数组来保存弱引用者.

当使用外部数组时

  • s1 的成员变量referrers是指向外部数组的指针; out_of_line_ness为常量 2, 表示使用了外部数组; mask表示外部数组的长度 - 1; num_refs表示外部数组中被使用的数量; max_hash_displacement表示哈希最大偏移量
  • 此时 s2 的成员变量 inline_referrers 被清空
作者

千行

发布于

2020-04-23

更新于

2022-10-21

许可协议

评论