如果大小不超过 MMAP_THRESHOLD 则先通过 bin_index_up 函数计算 chunk 大小对应的 bin 数组下标 i 。然后通过 mal.binmap 获取下标大于等于 i 的非空 bin 。接下来是两种情况,如果没有则调用 expand_heap 函数扩展堆然后调用 alloc_rev 将新扩展的堆块和前面空闲的堆块合并然后跳出循环,否则调用 first_set 函数获取大于等于 i 的最小下标,然后利用 pretrim 或 unbin 将 chunk 从 bin 链表中取出,最终也会跳出循环。这两种情况最终都会调用 trim 函数,这个函数的作用是从 c 上切下一块 chunk 用于内存分配,剩下的释放掉。
void *malloc(size_t n) { structchunk *c; int i, j;
if (adjust_size(&n) < 0) return0;
if (n > MMAP_THRESHOLD) { size_t len = n + OVERHEAD + PAGE_SIZE - 1 & -PAGE_SIZE; char *base = __mmap(0, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (base == (void *)-1) return0; c = (void *)(base + SIZE_ALIGN - OVERHEAD); c->csize = len - (SIZE_ALIGN - OVERHEAD); c->psize = SIZE_ALIGN - OVERHEAD; return CHUNK_TO_MEM(c); }
i = bin_index_up(n); for (;;) { uint64_t mask = mal.binmap & -(1ULL<<i); if (!mask) { c = expand_heap(n); if (!c) return0; if (alloc_rev(c)) { structchunk *x = c; c = PREV_CHUNK(c); NEXT_CHUNK(x)->psize = c->csize = x->csize + CHUNK_SIZE(c); } break; } j = first_set(mask); lock_bin(j); c = mal.bins[j].head; if (c != BIN_TO_CHUNK(j)) { if (!pretrim(c, n, i, j)) unbin(c, j); unlock_bin(j); break; } unlock_bin(j); }
/* Now patch up in case we over-allocated */ trim(c, n);
return CHUNK_TO_MEM(c); }
expand_heap
首先将需要扩展的大小 n 加上 SIZE_ALIGN(0x20),之后调用 __expand_heap 扩展堆并返回扩展后的内存的起始地址。
1 2 3 4 5 6 7 8 9 10 11 12
/* The argument n already accounts for the caller's chunk * overhead needs, but if the heap can't be extended in-place, * we need room for an extra zero-sized sentinel chunk. */ n += SIZE_ALIGN;
lock(heap_lock);
p = __expand_heap(&n); if (!p) { unlock(heap_lock); return0; }
/* If not just expanding existing space, we need to make a * new sentinel chunk below the allocated space. */ if (p != end) { /* Valid/safe because of the prologue increment. */ n -= SIZE_ALIGN; p = (char *) p + SIZE_ALIGN; w = MEM_TO_CHUNK(p); w->psize = 0 | C_INUSE; }
之后设置新扩展的 chunk 和下一个 chunk 的头部信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* Record new heap end and fill in footer. */ end = (char *) p + n; w = MEM_TO_CHUNK(end); w->psize = n | C_INUSE; w->csize = 0 | C_INUSE;
/* Fill in header, which may be new or may be replacing a * zero-size sentinel header at the old end-of-heap. */ w = MEM_TO_CHUNK(p); w->csize = n | C_INUSE;
/* Replace middle of large chunks with fresh zero pages */ if (reclaim) { uintptr_t a = (uintptr_t) self + SIZE_ALIGN + PAGE_SIZE - 1 & -PAGE_SIZE; uintptr_t b = (uintptr_t) next - SIZE_ALIGN & -PAGE_SIZE; #if 1 __madvise((void *) a, b - a, MADV_DONTNEED); #else __mmap((void *)a, b-a, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0); #endif }
size_t size = UNIT * size_classes[sc]; int i = 0, cnt; unsignedchar *p; structmeta *m = alloc_meta(); if (!m) return0; size_t usage = ctx.usage_by_class[sc]; size_t pagesize = PGSZ; int active_idx; if (sc < 9) { while (i < 2 && 4 * small_cnt_tab[sc][i] > usage) i++; cnt = small_cnt_tab[sc][i]; } else { // lookup max number of slots fitting in power-of-two size // from a table, along with number of factors of two we // can divide out without a remainder or reaching 1. cnt = med_cnt_tab[sc & 3];
// data structures don't support groups whose slot offsets // in units don't fit in 16 bits. while (size * cnt >= 65536 * UNIT) cnt >>= 1; }
// If we selected a count of 1 above but it's not sufficient to use // mmap, increase to 2. Then it might be; if not it will nest. if (cnt == 1 && size * cnt + UNIT <= pagesize / 2) cnt = 2;
// All choices of size*cnt are "just below" a power of two, so anything // larger than half the page size should be allocated as whole pages. if (size * cnt + UNIT > pagesize / 2) { // check/update bounce counter to start/increase retention // of freed maps, and inhibit use of low-count, odd-size // small mappings and single-slot groups if activated. int nosmall = is_bouncing(sc); account_bounce(sc); step_seq();
// since the following count reduction opportunities have // an absolute memory usage cost, don't overdo them. count // coarse usage as part of usage. if (!(sc & 1) && sc < 32) usage += ctx.usage_by_class[sc + 1];
// try to drop to a lower count if the one found above // increases usage by more than 25%. these reduced counts // roughly fill an integral number of pages, just not a // power of two, limiting amount of unusable space. if (4 * cnt > usage && !nosmall) { if (0) ; elseif ((sc & 3) == 1 && size * cnt > 8 * pagesize) cnt = 2; elseif ((sc & 3) == 2 && size * cnt > 4 * pagesize) cnt = 3; elseif ((sc & 3) == 0 && size * cnt > 8 * pagesize) cnt = 3; elseif ((sc & 3) == 0 && size * cnt > 2 * pagesize) cnt = 5; } size_t needed = size * cnt + UNIT; needed += -needed & (pagesize - 1);
// produce an individually-mmapped allocation if usage is low, // bounce counter hasn't triggered, and either it saves memory // or it avoids eagar slot allocation without wasting too much. if (!nosmall && cnt <= 7) { req += IB + UNIT; req += -req & (pagesize - 1); if (req < size + UNIT || (req >= 4 * pagesize && 2 * cnt > usage)) { cnt = 1; needed = req; } }
// use coarse size classes initially when there are not yet // any groups of desired size. this allows counts of 2 or 3 // to be allocated at first rather than having to start with // 7 or 5, the min counts for even size classes. if (!g && sc >= 4 && sc < 32 && sc != 6 && !(sc & 1) && !ctx.usage_by_class[sc]) { size_t usage = ctx.usage_by_class[sc | 1]; // if a new group may be allocated, count it toward // usage in deciding if we can use coarse class. if (!ctx.active[sc | 1] || (!ctx.active[sc | 1]->avail_mask && !ctx.active[sc | 1]->freed_mask)) usage += 3; if (usage <= 12) sc |= 1; g = ctx.active[sc]; }
之后从尝试在该 meta 中获取一个空闲 chunk 的下标,如果成功则更新 avail_mask 后直接跳转到 success 否则跳出循环。
1 2 3 4 5 6 7 8 9 10 11 12
for (;;) { mask = g ? g->avail_mask : 0; first = mask & -mask; if (!first) break; if (RDLOCK_IS_EXCLUSIVE || !MT) g->avail_mask = mask - first; elseif (a_cas(&g->avail_mask, mask, mask - first) != mask) continue; idx = a_ctz_32(first); goto success; } upgradelock();
如果 meta 没有空闲 chunk 则调用 alloc_slot 获取一个有空闲 chunk 的 meta 然后让 ctx.active 对应的 deque 头指向这个 meta 。
1 2 3 4 5 6
idx = alloc_slot(sc, n); if (idx < 0) { unlock(); return0; } g = ctx.active[sc];
最后如果成功获取空闲 chunk 的下标则调用 enframe 函数将该 chunk 取出。
1 2 3 4
success: ctr = ctx.mmap_counter; unlock(); return enframe(g, idx, n, ctr);
alloc_slot
1 2 3 4 5 6 7 8 9 10 11
staticintalloc_slot(int sc, size_t req) { uint32_t first = try_avail(&ctx.active[sc]); if (first) return a_ctz_32(first);
structmeta *g = alloc_group(sc, req); if (!g) return-1;
首先调用 try_avail 获取一个有空闲 chunk 的 meta 然后让让 ctx.active 对应的 deque 头指向这个 meta ,否则调用 alloc_group 申请一个新的 group 并且将这个 group 对应的 meta 加入到 deque 中(此时 deque 中就这一个 meta 因此 ctx.active 对应的 deque 头指向这个 meta )。
try_avail
首先如果 deque 为空则直接返回 0 。
1 2 3
structmeta *m = *pm; uint32_t first; if (!m) return0;
uint32_t mask = m->avail_mask; if (!mask) {...} first = mask & -mask; m->avail_mask = mask - first; return first;
如果当前的 meta 既没有空闲的 chunk 也没有释放的 chunk 则直接将该 meta 从 deque 中取出。为了充分利用空闲的 chunk ,无论当前 meta 有没有释放的 chunk 都会将 deque 的头指向下一个 meta 。另外如果 deque 为空会返回 0 。如果是正常情况如果有下一个 meta 则下一个 meta 一定会有可用或释放的 chunk 。
1 2 3 4 5 6 7 8
if (!m->freed_mask) { dequeue(pm, m); m = *pm; if (!m) return0; } else { m = m->next; *pm = m; }
如果下一个 meta 全部都是释放的 chunk 那么本着充分利用空闲 chunk 的原则会将 deque 的头指向下一个 meta 。
1 2 3 4 5 6 7 8 9
mask = m->freed_mask;
// skip fully-free group unless it's the only one // or it's a permanently non-freeable group if (mask == (2u << m->last_idx) - 1 && m->freeable) { m = m->next; *pm = m; mask = m->freed_mask; }
// activate more slots in a not-fully-active group // if needed, but only as a last resort. prefer using // any other group with free slots. this avoids // touching & dirtying as-yet-unused pages. if (!(mask & ((2u << m->mem->active_idx) - 1))) { if (m->next != m) { m = m->next; *pm = m; } else { int cnt = m->mem->active_idx + 2; int size = size_classes[m->sizeclass] * UNIT; int span = UNIT + size * cnt; // activate up to next 4k boundary while ((span ^ (span + size - 1)) < 4096) { cnt++; span += size; } if (cnt > m->last_idx + 1) cnt = m->last_idx + 1; m->mem->active_idx = cnt - 1; } } mask = activate_group(m); assert(mask); decay_bounces(m->sizeclass); }
// cycle offset within slot to increase interval to address // reuse, facilitate trapping double-free. int off = (p[-3] ? *(uint16_t *) (p - 2) + 1 : ctr) & 255; assert(!p[-4]); if (off > slack) { size_t m = slack; m |= m >> 1; m |= m >> 2; m |= m >> 4; off &= m; if (off > slack) off -= slack + 1; assert(off <= slack); } if (off) { // store offset in unused header at offset zero // if enframing at non-zero offset. *(uint16_t *) (p - 2) = off; p[-3] = 7 << 5; p += UNIT * off; // for nonzero offset there is no permanent check // byte, so make one. p[-4] = 0; }
最后如下图所示设置相关字段信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
staticinlinevoidset_size(unsignedchar *p, unsignedchar *end, size_t n) { int reserved = end - p - n; if (reserved) end[-reserved] = 0; if (reserved >= 5) { *(uint32_t *) (end - 4) = reserved; end[-5] = 0; reserved = 5; } p[-3] = (p[-3] & 31) + (reserved << 5); }
((unsignedchar *) p)[-3] = 255; // invalidate offset to group header, and cycle offset of // used region within slot if current offset is zero. *(uint16_t *) ((char *) p - 2) = 0;
// release any whole pages contained in the slot to be freed // unless it's a single-slot group that will be unmapped. if (((uintptr_t) (start - 1) ^ (uintptr_t) end) >= 2 * PGSZ && g->last_idx) { unsignedchar *base = start + (-(uintptr_t) start & (PGSZ - 1)); size_t len = (end - base) & -PGSZ; if (len) { int e = errno; madvise(base, len, MADV_FREE); errno = e; } }
如果加上将要释放的 chunk 该 group 中的所有 chunk 要么被释放要么空闲则跳出循环,否则更新 freed_mask 并返回。
1 2 3 4 5 6 7 8 9 10 11 12 13
// atomic free without locking if this is neither first or last slot for (;;) { uint32_t freed = g->freed_mask; uint32_t avail = g->avail_mask; uint32_t mask = freed | avail; assert(!(mask & self)); if (!freed || mask + self == all) break; if (!MT) g->freed_mask = freed + self; elseif (a_cas(&g->freed_mask, freed, freed + self) != freed) continue; return; }
如果跳出循环则会调用 nontrivial_free 释放 group 并返回需要 munmap 的内存的起始地址和大小,之后调用 munmap 释放这块内存。
1 2 3 4 5 6 7 8
wrlock(); structmapinfomi = nontrivial_free(g, idx); unlock(); if (mi.len) { int e = errno; munmap(mi.base, mi.len); errno = e; }
nontrivial_free
如果加上将要释放的 chunk 该 group 中的所有 chunk 要么被释放要么空闲并且 group 是可以释放的则首先判断 meta 是否在 active 这个 deque 中,如果在的话会将该 meta 从 deque 中取出。如果取出这个操作改变了 active 指针则将 active 当前指向的 meta 对应的 group 中的 chunk 调用 activate_group 函数激活。之后调用 free_group 将 chunk 所在的 group 释放并返回需要 munmap 的内存。
if (mask + self == (2u << g->last_idx) - 1 && okay_to_free(g)) { // any multi-slot group is necessarily on an active list // here, but single-slot groups might or might not be. if (g->next) { assert(sc < 48); int activate_new = (ctx.active[sc] == g); dequeue(&ctx.active[sc], g); if (activate_new && ctx.active[sc]) activate_group(ctx.active[sc]); } return free_group(g); }
如果该 chunk 所在的 meta 既没有释放的 chunk 也没有空闲的 chunk 则将该 meta 加入到 active 中。最后更新 freed_mask 。
1 2 3 4 5 6 7 8 9 10
elseif (!mask) { assert(sc < 48); // might still be active if there were no allocations // after last available slot was taken. if (ctx.active[sc] != g) { queue(&ctx.active[sc], g); } } a_or(&g->freed_mask, self); return (struct mapinfo){0};
free_group
首先更新 usage_by_class 。如果 group 是 mmap 得到的则返回 group 对应内存,否则调用 nontrivial_free 释放 group 所在的 chunk 。之后将 meta 释放。
staticintokay_to_free(struct meta *g) { int sc = g->sizeclass;
if (!g->freeable) return0;
// always free individual mmaps not suitable for reuse if (sc >= 48 || get_stride(g) < UNIT * size_classes[sc]) return1;
// always free groups allocated inside another group's slot // since recreating them should not be expensive and they // might be blocking freeing of a much larger group. if (!g->maplen) return1;
// if there is another non-full group, free this one to // consolidate future allocations, reduce fragmentation. if (g->next != g) return1;
// free any group in a size class that's not bouncing if (!is_bouncing(sc)) return1;
// if usage is high enough that a larger count should be // used, free the low-count group so a new one will be made. if (9 * cnt <= usage && cnt < 20) return1;
// otherwise, keep the last group in a bouncing class. return0; }