Linux堆基础知识(2)
无    883    1    3
mut3p1g

通过上一节的分析,已经对堆的基本结构有了大致的了解,那么下面就是来看看其关键的操作:内存分配及回收。由于分配的实现代码太长了,将在后面进行分析,这一节主要对内存回收进行分析。

0x01 分配区的初始化

上一节最后提到了一个结构体malloc_state,用来分配区,而其简化后就是mstate

  1. 13 typedef struct malloc_state *mstate;

然后就是分配区实例av的初始化了

  1. 1813 static void
  2. 1814 malloc_init_state (mstate av)
  3. 1815 {
  4. 1816 int i;
  5. 1817 mbinptr bin;
  6. 1818
  7. 1819 /* Establish circular links for normal bins */
  8. 1820 for (i = 1; i < NBINS; ++i) // 首先遍历bins
  9. 1821 {
  10. 1822 bin = bin_at (av, i); // 获取每个bin的链表
  11. 1823 bin->fd = bin->bk = bin; // 初始化每个链表为空,即头尾都指向自己
  12. 1824 }
  13. 1825
  14. 1826 #if MORECORE_CONTIGUOUS
  15. 1827 if (av != &main_arena)
  16. 1828 #endif
  17. 1829 set_noncontiguous (av); // 只有主分配才能分配连续的虚拟地址空间,所以对于非主分区需要设置为非连续的虚拟地址空间
  18. 1830 if (av == &main_arena) // 如果是主分区,则需要初始化fastbins,且只会初始化这么一次,因为每个进程只有一个主分配区
  19. 1831 set_max_fast (DEFAULT_MXFAST);
  20. 1832 av->flags |= FASTCHUNKS_BIT; // 表示存在fastbins
  21. 1833
  22. 1834 av->top = initial_top (av); // 初始化top
  23. 1835 }

0x02 其他函数

1. malloc_consolidate

这个函数的作用就是将fastbin合并后置入unsorted bin,一般调用的情况有以下几种:

  1. 1. freechunk时,该chunk合并前后空闲块后的大小超过了fastbin的收缩阈值。(一般与top合并时会触发)
  2. 2. malloc的大小在smallbin范围内,若对应的smallbin没初始化的时候。
  3. 3. malloc的大小在largebin范围内。
  4. 4. 当各种bins(包括topchunk)都无法满足malloc的要求时,如果发现仍然存在fastbin,则需要合并fastbin并重新malloc一次。

同时有下面两点需要注意的:

  1. 1. malloc_consolidate在合并fastbin的过程中没有对其size进行校验
  2. 2. malloc_consolidate将合并后生成的chunk插入到unsorted bin头部

1) 初始化

首先通过判断get_max_fast是否为0来判断ptmalloc是否初始化了,如果没有还需要初始化。之后获取unsorted bin,接下来要用。

  1. 4429 static void malloc_consolidate(mstate av)
  2. 4430 {
  3. 4431 mfastbinptr* fb; /* current fastbin being consolidated */
  4. 4432 mfastbinptr* maxfb; /* last fastbin (for loop control) */
  5. 4433 mchunkptr p; /* current chunk being consolidated */
  6. 4434 mchunkptr nextp; /* next chunk to consolidate */
  7. 4435 mchunkptr unsorted_bin; /* bin header */
  8. 4436 mchunkptr first_unsorted; /* chunk to link to */
  9. 4437
  10. 4438 /* These have same use as in free() */
  11. 4439 mchunkptr nextchunk;
  12. 4440 INTERNAL_SIZE_T size;
  13. 4441 INTERNAL_SIZE_T nextsize;
  14. 4442 INTERNAL_SIZE_T prevsize;
  15. 4443 int nextinuse;
  16. 4444 mchunkptr bck;
  17. 4445 mchunkptr fwd;
  18. 4446
  19. 4447 /*
  20. 4448 If max_fast is 0, we know that av hasn't
  21. 4449 yet been initialized, in which case do so below
  22. 4450 */
  23. 4451
  24. 4452 if (get_max_fast () != 0) { // 当前分配区已初始化
  25. 4453 clear_fastchunks(av); // 清除当前分配区存在fastbin的标志,因为后面就会把所有的fastbin全加入unsorted bin中了
  26. 4454
  27. 4455 unsorted_bin = unsorted_chunks(av); // 获得unsorted bin
  28. .....
  29. 4520 }
  30. 4521 else {
  31. 4522 malloc_init_state(av); // 初始化分配区
  32. 4523 check_malloc_state(av);
  33. 4524 }
  34. 4525 }

2) 将fastbin与前后空闲块合并

合并的过程和之前分析的合并也一样,判断每个fastbin前后是否空闲,然后进行合并。

  1. 4457 /*
  2. 4458 Remove each chunk from fast bin and consolidate it, placing it
  3. 4459 then in unsorted bin. Among other reasons for doing this,
  4. 4460 placing in unsorted bin avoids needing to calculate actual bins
  5. 4461 until malloc is sure that chunks aren't immediately going to be
  6. 4462 reused anyway.
  7. 4463 */
  8. 4464
  9. 4465 maxfb = &fastbin (av, NFASTBINS - 1); //获取最大的fastbin
  10. 4466 fb = &fastbin (av, 0); // 获得最小的fastbins
  11. 4467 do {
  12. 4468 p = atomic_exchange_acq (fb, NULL); // p指向fb的第一个fastbin
  13. 4469 if (p != 0) { // 如果当前分配区存在fastbin
  14. 4470 do {
  15. 4471 check_inuse_chunk(av, p); // 判断p是否在使用中(因为free一个fastbin时并不会将它的inuse位去掉)
  16. 4472 nextp = p->fd;// 获得p的下一个chunk的指针
  17. 4473
  18. 4474 /* Slightly streamlined version of consolidation code in free() */
  19. 4475 size = chunksize (p);
  20. 4476 nextchunk = chunk_at_offset(p, size);
  21. 4477 nextsize = chunksize(nextchunk);
  22. 4478
  23. 4479 if (!prev_inuse(p)) { //如果p的上一个chunk也不在使用中则合并
  24. 4480 prevsize = prev_size (p);
  25. 4481 size += prevsize;
  26. 4482 p = chunk_at_offset(p, -((long) prevsize));
  27. 4483 unlink(av, p, bck, fwd);
  28. 4484 }
  29. 4485
  30. 4486 if (nextchunk != av->top) {
  31. 4487 nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
  32. 4488
  33. 4489 if (!nextinuse) { //如果p的下一个chunk不在使用中且不是topchunk,则合并
  34. 4490 size += nextsize;
  35. 4491 unlink(av, nextchunk, bck, fwd);
  36. 4492 } else
  37. 4493 clear_inuse_bit_at_offset(nextchunk, 0); // 标记下一块不在使用中了
  38. 4494 .......
  39. 4508 }
  40. 4509
  41. 4510 else { // 如果下一块是topchunk
  42. 4511 size += nextsize;
  43. 4512 set_head(p, size | PREV_INUSE);
  44. 4513 av->top = p;
  45. 4514 }
  46. 4515
  47. 4516 } while ( (p = nextp) != 0); // fastbin循环处理
  48. 4517
  49. 4518 }
  50. 4519 } while (fb++ != maxfb);

3) 将合并后的chunk加入unsorted bin

这个插入操作也是和之前一样的,将合并后的chunk插入到unsorted bin的第一个,同时设置一下相关标志。

  1. 4495 first_unsorted = unsorted_bin->fd; // 获得第一个unsorted bin
  2. 4496 unsorted_bin->fd = p; // 将当前p插入为第一个unsorted chunk
  3. 4497 first_unsorted->bk = p;
  4. 4498
  5. 4499 if (!in_smallbin_range (size)) { // 如果合并后的chunk位large bin则还需设置size链
  6. 4500 p->fd_nextsize = NULL;
  7. 4501 p->bk_nextsize = NULL;
  8. 4502 }
  9. 4503
  10. 4504 set_head(p, size | PREV_INUSE); // 设置当前chunk的前一chunk为使用中
  11. 4505 p->bk = unsorted_bin;
  12. 4506 p->fd = first_unsorted;
  13. 4507 set_foot(p, size);

2. unlink

unlink的作用就是将某个chunk从它所在的链表中取出来,由于它知道自己的双向链表中前面和后面的chunk的地址,所以直接将前后两个chunk的指向更改一下就可以了。

  1. 1404 #define unlink(AV, P, BK, FD) { \
  2. 1405 if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \ // 当前chunk的size得和下一个chunk记录的prev_size相同
  3. 1406 malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV); \
  4. 1407 FD = P->fd; \
  5. 1408 BK = P->bk; \
  6. 1409 if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \//防止doublefree
  7. 1410 malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
  8. 1411 else { \
  9. 1412 FD->bk = BK; \
  10. 1413 BK->fd = FD; \
  11. 1414 if (!in_smallbin_range (chunksize_nomask (P)) \ //如果是largebin,下面就是将chunk从这条链中去除
  12. 1415 && __builtin_expect (P->fd_nextsize != NULL, 0)) { \
  13. 1416 if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
  14. 1417 || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
  15. 1418 malloc_printerr (check_action, \
  16. 1419 "corrupted double-linked list (not small)", \
  17. 1420 P, AV); \
  18. 1421 if (FD->fd_nextsize == NULL) { \
  19. 1422 if (P->fd_nextsize == P) \
  20. 1423 FD->fd_nextsize = FD->bk_nextsize = FD; \
  21. 1424 else { \
  22. 1425 FD->fd_nextsize = P->fd_nextsize; \
  23. 1426 FD->bk_nextsize = P->bk_nextsize; \
  24. 1427 P->fd_nextsize->bk_nextsize = FD; \
  25. 1428 P->bk_nextsize->fd_nextsize = FD; \
  26. 1429 } \
  27. 1430 } else { \
  28. 1431 P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
  29. 1432 P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
  30. 1433 } \
  31. 1434 } \
  32. 1435 } \
  33. 1436 }

0x03 内存释放

由于free的代码稍微短一些所以我这里先分析内存释放,其核心代码主要是__libc_free,下面开始分析。

1. __libc_free

  1. 3090 void
  2. 3091 __libc_free (void *mem) // 首先需要注意free的是mem不是chunk,即malloc后返回的地址
  3. 3092 {
  4. 3093 mstate ar_ptr;
  5. 3094 mchunkptr p; /* chunk corresponding to mem */
  6. 3095
  7. 3096 void (*hook) (void *, const void *)
  8. 3097 = atomic_forced_read (__free_hook);
  9. 3098 if (__builtin_expect (hook != NULL, 0))
  10. 3099 {
  11. 3100 (*hook)(mem, RETURN_ADDRESS (0));
  12. 3101 return;
  13. 3102 }
  14. 3103
  15. 3104 if (mem == 0) /* free(0) has no effect */
  16. 3105 return;
  17. 3106
  18. 3107 p = mem2chunk (mem);// 将mem转为chunk
  19. 3108
  20. 3109 if (chunk_is_mmapped (p)) //如果待释放的chunk是通过mmap申请的 /* release mmapped memory. */
  21. 3110 {
  22. 3111 /* See if the dynamic brk/mmap threshold needs adjusting.
  23. 3112 Dumped fake mmapped chunks do not affect the threshold. */
  24. 3113 if (!mp_.no_dyn_threshold // mmap的阈值是动态的
  25. 3114 && chunksize_nomask (p) > mp_.mmap_threshold // 当前free的chunk大小超过了原来的阈值
  26. 3115 && chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX // 当前free的chunk大小小于阈值的最大值(4*1024*1024*sizeof(long))
  27. 3116 && !DUMPED_MAIN_ARENA_CHUNK (p)) // 当前chunk不在dumped_main_arena里
  28. 3117 {
  29. 3118 mp_.mmap_threshold = chunksize (p); // 更新mmap_threshold为当前free的chunk的大小
  30. 3119 mp_.trim_threshold = 2 * mp_.mmap_threshold; // 更新trim_threshold为mmap_threshold的两倍
  31. 3120 LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
  32. 3121 mp_.mmap_threshold, mp_.trim_threshold);
  33. 3122 }
  34. 3123 munmap_chunk (p); // 释放mmap申请的空间
  35. 3124 return;
  36. 3125 }
  37. 3126
  38. 3127 MAYBE_INIT_TCACHE ();
  39. 3128
  40. 3129 ar_ptr = arena_for_chunk (p); // 获得p对应的分配区
  41. 3130 _int_free (ar_ptr, p, 0); // 释放p
  42. 3131 }

对这个函数总结一下:
1. 首先需要获取分配区的锁,以保证安全。
2. 接着将待释放的mem转换为chunk,方便处理。
3. 对通过mmap申请的空间调用munmap函数进行释放,同时对mmap分配mmap_threshold及收缩trim_threshold阈值进行调整。
4. 若不是通过mmap分配的空间,则调用_int_free进行释放。

2. _int_free

1) 变量申明

下面都是后面会用上的临时变量,到时候用到了可以回来看看定义。

  1. 4132 static void
  2. 4133 _int_free (mstate av, mchunkptr p, int have_lock)
  3. 4134 {
  4. 4135 INTERNAL_SIZE_T size; /* its size */
  5. 4136 mfastbinptr *fb; /* associated fastbin */
  6. 4137 mchunkptr nextchunk; /* next contiguous chunk */
  7. 4138 INTERNAL_SIZE_T nextsize; /* its size */
  8. 4139 int nextinuse; /* true if nextchunk is used */
  9. 4140 INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */
  10. 4141 mchunkptr bck; /* misc temp for linking */
  11. 4142 mchunkptr fwd; /* misc temp for linking */
  12. 4143
  13. 4144 const char *errstr = NULL;
  14. 4145 int locked = 0;
  15. 4146
  16. 4147 size = chunksize (p); // 首先获取待释放空间的大小

2) 安全检查

这里主要做了下面三个安全检查:
1. p的地址是否合法,是否对齐
2. p的size是否大于等于MINSIZE且对齐
3. p的下一个chunkPREV_INUSE是否为真

  1. 4149 /* Little security check which won't hurt performance: the
  2. 4150 allocator never wrapps around at the end of the address space.
  3. 4151 Therefore we can exclude some size values which might appear
  4. 4152 here by accident or by "design" from some intruder. */
  5. 4153 if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0) // 指针地址不能溢出
  6. 4154 || __builtin_expect (misaligned_chunk (p), 0)) // chunk没有对齐
  7. 4155 {
  8. 4156 errstr = "free(): invalid pointer";
  9. 4157 errout:
  10. 4158 if (!have_lock && locked)
  11. 4159 __libc_lock_unlock (av->mutex);
  12. 4160 malloc_printerr (check_action, errstr, chunk2mem (p), av);
  13. 4161 return;
  14. 4162 }
  15. 4163 /* We know that each chunk is at least MINSIZE bytes in size or a
  16. 4164 multiple of MALLOC_ALIGNMENT. */
  17. 4165 if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size))) // chunk的大小小于MINSIZE或者没有对齐
  18. 4166 {
  19. 4167 errstr = "free(): invalid size";
  20. 4168 goto errout;
  21. 4169 }
  22. 4170
  23. 4171 check_inuse_chunk(av, p); // 判断chunk是否在使用中(我们释放的必须是在使用中的)
  24. 4172

3) fastbin

a. fastbin check

前提就是当前待释放的chunk属于fastbin,且它的下一个chunk不能是top-chunk

  1. 4192 if ((unsigned long)(size) <= (unsigned long)(get_max_fast ()) // chunk大小小于fastbin的最大值
  2. 4193
  3. 4194 #if TRIM_FASTBINS
  4. 4195 /*
  5. 4196 If TRIM_FASTBINS set, don't place chunks
  6. 4197 bordering top into fastbins
  7. 4198 */
  8. 4199 && (chunk_at_offset(p, size) != av->top) // chunk的下一个不能是top
  9. 4200 #endif
  10. 4201 ) {
b. next_chunk check & lock

下一个chunk的大小不能太小,也不能太大。关于锁的部分我也不太懂,所以不具体分析了。

  1. 4203 if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size))
  2. 4204 <= 2 * SIZE_SZ, 0)//太小
  3. 4205 || __builtin_expect (chunksize (chunk_at_offset (p, size))
  4. 4206 >= av->system_mem, 0))//太大
  5. 4207 {
  6. 4208 /* We might not have a lock at this point and concurrent modifications
  7. 4209 of system_mem might have let to a false positive. Redo the test
  8. 4210 after getting the lock. */
  9. 4211 if (have_lock
  10. 4212 || ({ assert (locked == 0);
  11. 4213 __libc_lock_lock (av->mutex);
  12. 4214 locked = 1;
  13. 4215 chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ
  14. 4216 || chunksize (chunk_at_offset (p, size)) >= av->system_mem;
  15. 4217 }))
  16. 4218 {
  17. 4219 errstr = "free(): invalid next size (fast)";
  18. 4220 goto errout;
  19. 4221 }
  20. 4222 if (! have_lock)
  21. 4223 {
  22. 4224 __libc_lock_unlock (av->mutex);
  23. 4225 locked = 0;
  24. 4226 }
  25. 4227 }
c. Init for fastbin
  1. 1904 free_perturb (char *p, size_t n)
  2. 1905 {
  3. 1906 if (__glibc_unlikely (perturb_byte))
  4. 1907 memset (p, perturb_byte, n);
  5. 1908 }
  6. 4229 free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); // 如果设置了perturb_byte,则将fastbin中的content全部赋值为perturb_byte
  7. 4230
  8. 4231 set_fastchunks(av); // 设置分配区av中存在空闲fastbin的
  9. 4232 unsigned int idx = fastbin_index(size); // 获取待释放chunk对应的fastbin_index
  10. 4233 fb = &fastbin (av, idx); // 获取待释放chunk对应的fastbin
d. link p to its fastbin

这里fastbin的处理使用了lock-free技术,而该技术基于CAS(compare-and-swap)原子操作。具体算法大家可以学习一下。这里将释放的chunk加入到了对应大小的fastbins的第一个。当释放的是对应fastbins的链表头的话,则会报double free错误;否则可以多次释放,也可以多次分配。

  1. 4235 /* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */
  2. 4236 mchunkptr old = *fb, old2; // old指对应fastbins的链表头
  3. 4237 unsigned int old_idx = ~0u;
  4. 4238 do
  5. 4239 {
  6. 4240 /* Check that the top of the bin is not the record we are going to add
  7. 4241 (i.e., double free). */
  8. 4242 if (__builtin_expect (old == p, 0)) // 这里只判断了当前释放的是不是fastbins对应的链表头
  9. 4243 {
  10. 4244 errstr = "double free or corruption (fasttop)";
  11. 4245 goto errout;
  12. 4246 }
  13. 4247 /* Check that size of fastbin chunk at the top is the same as
  14. 4248 size of the chunk that we are adding. We can dereference OLD
  15. 4249 only if we have the lock, otherwise it might have already been
  16. 4250 deallocated. See use of OLD_IDX below for the actual check. */
  17. 4251 if (have_lock && old != NULL)
  18. 4252 old_idx = fastbin_index(chunksize(old));
  19. 4253 p->fd = old2 = old;
  20. 4254 }
  21. 4255 while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2);

e. last check
最后一个主要是要求表头和释放的chunk所属的fastbin相同。

  1. 4257 if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0))
  2. 4258 {
  3. 4259 errstr = "invalid fastbin entry (free)";
  4. 4260 goto errout;
  5. 4261 }
  6. 4262 }

4) unsorted bin

对于不是mmap分配且不是fastbin的空间,释放后都会直接加入unsorted bin

a. check

这里主要有这么几个判断:
1. 当前释放的chunk不能是topchunk,因为topchunk本来就是空闲的。
2. 当前释放的chunk的下一个chunk不能超过topchunk的结束地址。
3. 通过查看下一个chunkprev_size,发现当前当前chunk并不在使用状态。
4. 下一个chunk的大小过大或过小,不符合系统设置。

  1. 4264 /*
  2. 4265 Consolidate other non-mmapped chunks as they arrive.
  3. 4266 */
  4. 4267
  5. 4268 else if (!chunk_is_mmapped(p)) { // 当前chunk不是通过mmap申请的
  6. 4269 if (! have_lock) {
  7. 4270 __libc_lock_lock (av->mutex);
  8. 4271 locked = 1;
  9. 4272 }
  10. 4273
  11. 4274 nextchunk = chunk_at_offset(p, size);
  12. 4275
  13. 4276 /* Lightweight tests: check whether the block is already the
  14. 4277 top block. */
  15. 4278 if (__glibc_unlikely (p == av->top))// 当前释放的chunk不是topchunk
  16. 4279 {
  17. 4280 errstr = "double free or corruption (top)";
  18. 4281 goto errout;
  19. 4282 }
  20. 4283 /* Or whether the next chunk is beyond the boundaries of the arena. */
  21. 4284 if (__builtin_expect (contiguous (av)
  22. 4285 && (char *) nextchunk
  23. 4286 >= ((char *) av->top + chunksize(av->top)), 0))// 下一个chunk超过了top的结束地址
  24. 4287 {
  25. 4288 errstr = "double free or corruption (out)";
  26. 4289 goto errout;
  27. 4290 }
  28. 4291 /* Or whether the block is actually not marked used. */
  29. 4292 if (__glibc_unlikely (!prev_inuse(nextchunk))) // 当前chunk并不在使用中
  30. 4293 {
  31. 4294 errstr = "double free or corruption (!prev)";
  32. 4295 goto errout;
  33. 4296 }
  34. 4297
  35. 4298 nextsize = chunksize(nextchunk);
  36. 4299 if (__builtin_expect (chunksize_nomask (nextchunk) <= 2 * SIZE_SZ, 0)
  37. 4300 || __builtin_expect (nextsize >= av->system_mem, 0))// 下一个chunk的大小不合法
  38. 4301 {
  39. 4302 errstr = "free(): invalid next size (normal)";
  40. 4303 goto errout;
  41. 4304 }
b. consolidate backward

这里主要是判断前一个chunk是否在使用中,如果不在则进行合并,同时将地址设置为前一个chunk的地址。

  1. 4306 free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); // 同fastbin相应处理
  2. 4307
  3. 4308 /* consolidate backward */
  4. 4309 if (!prev_inuse(p)) { // 如果上一个chunk空闲则合并
  5. 4310 prevsize = prev_size (p);
  6. 4311 size += prevsize; // size加起来
  7. 4312 p = chunk_at_offset(p, -((long) prevsize)); // 将p设置为上一个chunk的地址
  8. 4313 unlink(av, p, bck, fwd); // 调用unlink进行合并
  9. 4314 }
c. consolidate forward

这里主要是判断后一个chunk是否在使用中,如果不在则进行合并。

  1. 4316 if (nextchunk != av->top) { // 待释放的chunk不与top相邻
  2. 4317 /* get and clear inuse bit */
  3. 4318 nextinuse = inuse_bit_at_offset(nextchunk, nextsize); // 查看下一个chunk是否在使用中
  4. 4319
  5. 4320 /* consolidate forward */
  6. 4321 if (!nextinuse) { // 如果不在使用中则进行合并
  7. 4322 unlink(av, nextchunk, bck, fwd);
  8. 4323 size += nextsize; //size加起来
  9. 4324 } else
  10. 4325 clear_inuse_bit_at_offset(nextchunk, 0); // 如果下一个chunk在使用中,就把设置其上一块为空闲
  11. 4326
d. place the chunk=> unsorted bin

由于ptmalloc设计思路就是不会存在连续两块chunk是空闲的,所以在释放某个chunk的时候只需要查看其前两个chunk是否空闲并合并就可以了。合并完成后就要将其插入为unsorted bin的第一个bin

  1. 4327 /*
  2. 4328 Place the chunk in unsorted chunk list. Chunks are
  3. 4329 not placed into regular bins until after they have
  4. 4330 been given one chance to be used in malloc.
  5. 4331 */
  6. 4332
  7. 4333 bck = unsorted_chunks(av); // 获得当前分配区的unsorted bin
  8. 4334 fwd = bck->fd; // 获得unsorted bin的第一个
  9. 4335 if (__glibc_unlikely (fwd->bk != bck))
  10. 4336 {
  11. 4337 errstr = "free(): corrupted unsorted chunks";
  12. 4338 goto errout;
  13. 4339 }
  14. 4340 p->fd = fwd; // 将p插入到第一个
  15. 4341 p->bk = bck;
  16. 4342 if (!in_smallbin_range(size)) // 如果是largebin
  17. 4343 {
  18. 4344 p->fd_nextsize = NULL;
  19. 4345 p->bk_nextsize = NULL;
  20. 4346 }
  21. 4347 bck->fd = p;
  22. 4348 fwd->bk = p;
  23. 4349
  24. 4350 set_head(p, size | PREV_INUSE); // 设置当前空闲chunk的上一块是在使用中
  25. 4351 set_foot(p, size); // 通过设置当前chunk下一块的prev_inuse,标记当前chunk是空闲的
  26. 4352
  27. 4353 check_free_chunk(av, p);
  28. 4354 }
  29. 4355

5) topchunk

如果待释放chunk紧贴着topchunk,则与top合并,并将top设置为当前chunk。这里没有清空。

  1. 4356 /*
  2. 4357 If the chunk borders the current high end of memory,
  3. 4358 consolidate into top
  4. 4359 */
  5. 4360
  6. 4361 else {
  7. 4362 size += nextsize;
  8. 4363 set_head(p, size | PREV_INUSE);
  9. 4364 av->top = p; // top设置为当前待释放的chunk
  10. 4365 check_chunk(av, p);
  11. 4366 }
  12. 4367

6) trim heap

下面对堆进行收缩,主要有下面几个操作:
1. 如果释放的空间大于fastbin的收缩阈值且fastbin不为空,则会调用malloc_consolidatefastbin合并且放入unsorted bin
2. 如果是主分配区,且释放的空间大于系统内存收缩阈值,则调用systrim收缩系统内存。
3. 如果是非主分区,则始终调用heap_trim收缩堆。

  1. 4368 /*
  2. 4369 If freeing a large space, consolidate possibly-surrounding
  3. 4370 chunks. Then, if the total unused topmost memory exceeds trim
  4. 4371 threshold, ask malloc_trim to reduce top.
  5. 4372
  6. 4373 Unless max_fast is 0, we don't know if there are fastbins
  7. 4374 bordering top, so we cannot tell for sure whether threshold
  8. 4375 has been reached unless fastbins are consolidated. But we
  9. 4376 don't want to consolidate on each free. As a compromise,
  10. 4377 consolidation is performed if FASTBIN_CONSOLIDATION_THRESHOLD
  11. 4378 is reached.
  12. 4379 */
  13. 4380
  14. 4381 if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
  15. 4382 if (have_fastchunks(av)) // 如果size超过了fastbin的合并阈值(默认64KB),且fastbin不为空,则调用malloc_consolidate合并fastbin
  16. 4383 malloc_consolidate(av);
  17. 4384
  18. 4385 if (av == &main_arena) { // 如果是主分配区
  19. 4386 #ifndef MORECORE_CANNOT_TRIM
  20. 4387 if ((unsigned long)(chunksize(av->top)) >=
  21. 4388 (unsigned long)(mp_.trim_threshold)) //如果size超过了系统内存收缩阈值则调用systrim收缩
  22. 4389 systrim(mp_.top_pad, av);
  23. 4390 #endif
  24. 4391 } else { // 如果是非主分配区,则始终调用heap_trim收缩堆
  25. 4392 /* Always try heap_trim(), even if the top chunk is not
  26. 4393 large, because the corresponding heap might go away. */
  27. 4394 heap_info *heap = heap_for_ptr(top(av));
  28. 4395
  29. 4396 assert(heap->ar_ptr == av);
  30. 4397 heap_trim(heap, mp_.top_pad);
  31. 4398 }
  32. 4399 }
  33. 4400
  34. 4401 if (! have_lock) {
  35. 4402 assert (locked);
  36. 4403 __libc_lock_unlock (av->mutex);
  37. 4404 }
  38. 4405 }

7) mmap

对于mmap申请的空间,则调用munmap_chunk释放。

  1. 4410 else {
  2. 4411 munmap_chunk (p);
  3. 4412 }
  4. 4413 }

8) 总结

free的实现相对比较简单,主要就是对fastbinunsorted bin的处理,下面对这两个的校验和流程进行一下总结。

a. 通用检查
  1. p的地址是否合法,是否对齐
  2. p的size是否大于等于MINSIZE且对齐
  3. p的下一个chunkPREV_INUSE是否为真
  4. p的下一个chunk不能太小(≤2*SIZE_SZ)或太大(≥av->system_mem)
b. fastbin
  • 检查
    1. p的大小小于fastbin的最大值global_max_fast
    2. p不能是对应fastbinsfasttop(应该是将fastbin全部遍历一遍看看在不在里面)
    3. p对应fastbinsfasttop下标与p下标相同
  • 流程
    通过校验后,p成为对应fastbinsfasttop,并不修改p下一个chunkPREV_INUSE
c. unsorted bin
  • 检查
    1. p不能是topchunk
    2. p的下一个chunk不能超过topchunk的结束地址
  • 流程

    最后将合并的chunk插入为unsorted bin的第一个块
d. next chunk => top

如果下一个chunktopchunk,那么直接将两者合并成一个chunk,并将新的chunk设置为topchunk

觉得不错,点个赞?
Sign in to leave a comment.
No Leanote account ? Sign up now.
3 条评论
  • 有一个疑问,#define NFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) + 1) //10个 fastbins维护7条单链表,但是有10个数组,offset - 2,这里少了一个序号呀
    2019-09-04 20:04:53
  • 终于看完了第二部分哈哈哈
    2019-09-04 20:04:05
  • 很棒!
    2019-08-26 19:35:57
文章目录