Glibc内存管理--ptmalloc2源代码分析(十四)

.2.2  Large bins

SIZE_SZ 4B 的平台上,大于等于 512B 的空闲 chunk ,或者,在 SIZE_SZ 8B 的平台上,大小大于等于 1024B 的空闲 chunk ,由 sorted bins 管理。 Large bins 一共包括 63 bin ,每个 bin 中的 chunk 大小不是一个固定公差的等差数列,而是分成 6 bin ,每组 bin 是一个固定公差的等差数列,每组的 bin 数量依次为 32 16 8 4 2 1 ,公差依次为 64B 512B 4096B 32768B 262144B 等。

SIZE_SZ 4B 的平台为例,第一个 large bin 的起始 chunk 大小为 512B ,共 32 bin ,公差为 64B ,等差数列满足如下关系:

Chunk_size=512 + 64 * index

第二个 large bin 的起始 chunk 大小为第一组 bin 的结束 chunk 大小,满足如下关系:

Chunk_size=512 + 64 * 32 + 512 * index

同理,我们可计算出每个 bin 的起始 chunk 大小和结束 chunk 大小。这些 bin 都是很有规律的,其实 small bins 也是满足类似规律, small bins 可以看着是公差为 8 的等差数列,一共有 64 bin (第 0 1bin 不存在),所以我们可以将 small bins large bins 存放在同一个包含 128 chunk 的数组上,数组的前一部分位 small bins ,后一部分为 large bins ,每个 bin index chunk 数组的下标,于是,我们可以根据数组下标计算出该 bin chunk 大小( small bins )或是 chunk 大小范围( large bins ),也可以根据需要分配内存块大小计算出所需 chunk 所属 bin index ptmalloc 使用了一组宏巧妙的实现了这种计算。

#define NBINS             128
#define NSMALLBINS         64
#define SMALLBIN_WIDTH    MALLOC_ALIGNMENT
#define MIN_LARGE_SIZE    (NSMALLBINS * SMALLBIN_WIDTH)

#define in_smallbin_range(sz)  \
  ((unsigned long)(sz) < (unsigned long)MIN_LARGE_SIZE)

#define smallbin_index(sz) \
  (SMALLBIN_WIDTH == 16 ? (((unsigned)(sz)) >> 4) : (((unsigned)(sz)) >> 3))

#define largebin_index_32(sz)                                                \
(((((unsigned long)(sz)) >>  6) <= 38)?  56 + (((unsigned long)(sz)) >>  6): \
 ((((unsigned long)(sz)) >>  9) <= 20)?  91 + (((unsigned long)(sz)) >>  9): \
 ((((unsigned long)(sz)) >> 12) <= 10)? 110 + (((unsigned long)(sz)) >> 12): \
 ((((unsigned long)(sz)) >> 15) <=  4)? 119 + (((unsigned long)(sz)) >> 15): \
 ((((unsigned long)(sz)) >> 18) <=  2)? 124 + (((unsigned long)(sz)) >> 18): \
                                        126)

// XXX It remains to be seen whether it is good to keep the widths of
// XXX the buckets the same or whether it should be scaled by a factor
// XXX of two as well.
#define largebin_index_64(sz)                                                \
(((((unsigned long)(sz)) >>  6) <= 48)?  48 + (((unsigned long)(sz)) >>  6): \
 ((((unsigned long)(sz)) >>  9) <= 20)?  91 + (((unsigned long)(sz)) >>  9): \
 ((((unsigned long)(sz)) >> 12) <= 10)? 110 + (((unsigned long)(sz)) >> 12): \
 ((((unsigned long)(sz)) >> 15) <=  4)? 119 + (((unsigned long)(sz)) >> 15): \
 ((((unsigned long)(sz)) >> 18) <=  2)? 124 + (((unsigned long)(sz)) >> 18): \
                                        126)

#define largebin_index(sz) \
  (SIZE_SZ == 8 ? largebin_index_64 (sz) : largebin_index_32 (sz))

#define bin_index(sz) \
 ((in_smallbin_range(sz)) ? smallbin_index(sz) : largebin_index(sz))

      bin_index(sz) 根据所需内存大小计算出所需 bin index ,如果所需内存大小属于 small bins 的大小范围,调用 smallbin_index(sz) ,否则调用 largebin_index(sz)) smallbin_index(sz) 的计算相当简单,如果 SIZE_SZ 4B ,则将 sz 除以 8 ,如果 SIZE_SZ 8B ,则将 sz 除以 16 ,也就是除以 small bins 中等差数列的公差。 largebin_index(sz) 的计算相对复杂一些,可以用如下的表格直观的显示 chunk 的大小范围与 bin index 的关系。以 SIZE_SZ 4B 的平台为例, chunk 大小与 bin index 的对应关系如下表所示:(博客编辑器太弱,不能全部贴出来)

开始( 字节)

结束(字节)

Bin index

0

7

不存在

8

15

不存在

16

23

2

24

31

3

32

39

4

40

47

5

48

55

6

56

63

7

64

71

8

72

79

9

80

87

10

88

95

11

96

103

12

104

111

13

112

119

14

120

127

15

128

135

16

136

143

17

144

151

18

152

159

19

160

167

20

168

175

21

176

183

22

184

191

23

192

199

24

200

207

25

208

215

26

216

223

27

224

231

28

232

239

29

240

247

30

248

255

31

256

263

32

    注意:上表是 chunk 大小与 bin index 的对应关系,如果对于用户要分配的内存大小 size ,必须先使用 checked_request2size(req, sz) 计算出 chunk 的大小,再使用 bin_index(sz) 计算出 chunk 所属的 bin index

         对于 SIZE_SZ 4B 的平台, bin[0] bin[1] 是不存在的,因为最小的 chunk 16B small bins 一共 62 个, large bins 一共 63 个,加起来一共 125 bin 。而 NBINS 定义为 128 ,其实 bin[0] bin[127] 都不存在, bin[1] unsorted bin chunk 链表头。

typedef struct malloc_chunk* mbinptr;

/* addressing -- note that bin_at(0) does not exist */
#define bin_at(m, i) \
  (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2]))                           \
             - offsetof (struct malloc_chunk, fd))

/* analog of ++bin */
#define next_bin(b)  ((mbinptr)((char*)(b) + (sizeof(mchunkptr)<<1)))

/* Reminders about list directionality within bins */
#define first(b)     ((b)->fd)
#define last(b)      ((b)->bk)

/* Take a chunk off a bin list */
#define unlink(P, BK, FD) {                                            \
  FD = P->fd;                                                          \
  BK = P->bk;                                                          \
  if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                \
    malloc_printerr (check_action, "corrupted double-linked list", P); \
  else {                                                               \
    FD->bk = BK;                                                       \
    BK->fd = FD;                                                       \
    if (!in_smallbin_range (P->size)                                   \
        && __builtin_expect (P->fd_nextsize != NULL, 0)) {             \
      assert (P->fd_nextsize->bk_nextsize == P);                       \
      assert (P->bk_nextsize->fd_nextsize == P);                       \
      if (FD->fd_nextsize == NULL) {                                   \
        if (P->fd_nextsize == P)                                       \
          FD->fd_nextsize = FD->bk_nextsize = FD;                      \
        else {                                                         \
          FD->fd_nextsize = P->fd_nextsize;                            \
          FD->bk_nextsize = P->bk_nextsize;                            \
          P->fd_nextsize->bk_nextsize = FD;                            \
          P->bk_nextsize->fd_nextsize = FD;                            \
        }                                                              \
      } else {                                                         \
        P->fd_nextsize->bk_nextsize = P->bk_nextsize;                  \
        P->bk_nextsize->fd_nextsize = P->fd_nextsize;                  \
      }                                                                \
    }                                                                  \
  }                                                                    \
}

    bin_at(m, i) 通过 bin index 获得 bin 的链表头, chunk 中的 fb bk 用于将空闲 chunk 链入链表中,而对于每个 bin 的链表头,只需要这两个域就可以了, prev_size size 对链表都来说都没有意义,浪费空间, ptmalloc 为了节约这点内存空间,增大 cpu 高速缓存的命中率,在 bins 数组中只为每个 bin 预留了两个指针的内存空间用于存放 bin 的链表头的 fb bk 指针。

         bin_at(m, i) 的定义可以看出, bin[0] 不存在,以 SIZE_SZ 4B 的平台为例, bin[1] 的前 4B 存储的是指针 fb ,后 4B 存储的是指针 bk ,而 bin_at 返回的是 malloc_chunk 的指针,由于 fb malloc_chunk 的偏移地址为 offsetof (struct malloc_chunk, fd))=8 ,所以用 fb 的地址减去 8 就得到 malloc_chunk 的地址。但切记,对 bin 的链表头的 chunk ,一定不能修改 prev_size size 域,这两个域是与其他 bin 的链表头的 fb bk 内存复用的。

     宏 next_bin(b) 用于获得下一个 bin 的地址,根据前面的分析,我们知道只需要将当前 bin 的地址向后移动两个指针的长度就得到下一个 bin 的链表头地址。

     每个 bin 使用双向循环链表管理空闲 chunk bin 的链表头的指针 fb 指向第一个可用的 chunk ,指针 bk 指向最后一个可用的 chunk 。宏 first(b) 用于获得 bin 的第一个可用 chunk ,宏 last(b) 用于获得 bin 的最后一个可用的 chunk ,这两个宏便于遍历 bin ,而跳过 bin 的链表头。

     宏 unlink(P, BK, FD) 用于将 chunk 从所在的空闲链表中取出来,注意 large bins 中的空闲 chunk 可能处于两个双向循环链表中, unlink 时需要从两个链表中都删除。

猜你喜欢

转载自mqzhuang.iteye.com/blog/1064605