前言
本文以 《glibc 內存管理 ptmalooc 源碼代分析》 為參考對 malloc 與 free 的過程進行分析,分析中對內存分配管理的基礎知識不再做介紹,適合具有一定基礎的讀者。由於書中 glibc 的版本過老,glibc 2.31 源碼相比有許多不同之處,這里我結合自己對源碼的理解給出注釋,如果錯誤之處還請師傅們指正。
對於 malloc 過程,以 __libc_malloc 函數為入口開始分析,對於 free 過程,以 __libc_free 函數為入口分析。分析過程中遇到的函數和結構體,都會在后文給出源碼並進行分析,大家 ctrl + f 即可。
其中 _int_malloc() _int_free() 兩個函數是核心部分,會重點分析。
函數
__libc_malloc (size_t bytes)
void * __libc_malloc (size_t bytes)
{
/* mstate類型對應的結構體是 malloc_state */
mstate ar_ptr;
void *victim;
_Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
"PTRDIFF_MAX is not more than half of SIZE_MAX");
/* 如果存在__malloc_hook,則調用 hook 函數 */
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
/* 使用 tcache 機制的情況 */
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
/* 判斷請求分配字節的大小,在 64 位的情況下,bytes 不能大於 0x7fffffffffffffff;*/
/* 在 32 位的情況下,bytes 不能超過 0x7fffffff。函數中也會調用 request2size 來 */
/* 計算 bytes 數據需要分配的內存大小,當 bytes 數據的大小比最小 chunk 要還小時,*/
/* 按最小 chunk 的大小分配;當 bytes 數據的大小比最小 chunk 大時,則分配滿足內存 */
/* 對齊要求的最小大小。將分配的大小賦值給 tbytes 返回。 */
if (!checked_request2size (bytes, &tbytes))
{
__set_errno (ENOMEM);
return NULL;
}
/* 計算 tbytes 大小所對應的 tcache 下標 */
size_t tc_idx = csize2tidx (tbytes);
/* 如果 tcache 還沒有被創建,則調用 tcache_init() 初始化 tcache */
MAYBE_INIT_TCACHE ();
DIAG_PUSH_NEEDS_COMMENT;
/* 這里的 mp_ 是 malloc_par 結構 */
/* 判斷 idx 是否在 tcache bins 的范圍內 */
/* 判斷 tcache 是否存在 */
/* 判斷 idx 對應的 tcache bins 中是否有空閑 tcache chunk */
if (tc_idx < mp_.tcache_bins
&& tcache
&& tcache->counts[tc_idx] > 0)
{
return tcache_get(tc_idx); /* 獲得對應大小的 chunk */
}
DIAG_POP_NEEDS_COMMENT;
#endif
/* 沒有啟用多線程的情況 */
if (SINGLE_THREAD_P)
{
victim = _int_malloc (&main_arena, bytes); /* 調用 _int_malloc 函數分配內存 */
/* 這里為什么是 “!victim” 我也沒有理解,后面兩個分別是*/
/* 當前 chunk 是從 mmap 分配的或當前 chunk 是從主分配區分配的 */
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
&main_arena == arena_for_chunk (mem2chunk (victim)));
return victim; /* 將成功分配的內存指針返回 */
}
/* 啟用多線程的情況 */
arena_get (ar_ptr, bytes); /* 獲取分配區 */
victim = _int_malloc (ar_ptr, bytes); /* 同上 */
/* Retry with another arena only if we were able to find a usable arena
before. */
/* 如果成功獲取分配區,但是分配內存失敗,可能是 mmap 區域的內存耗盡等多種原因 */
/* 不同的原因有不同的解決方法,比如更換分配區等等 */
/* 所以這里重新進行了獲取分配區和分配內存操作,確保內存分配成功 */
if (!victim && ar_ptr != NULL)
{
LIBC_PROBE (memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
/* 這里是釋放線程鎖,不用管它 */
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
return victim;
}
tcache_init(viod)
static void tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
/* 計算 tcahce_perthread_struct 結構大小 */
const size_t bytes = sizeof (tcache_perthread_struct);
/* 判斷 tcache 是否被禁用,禁用情況下直接返回 */
if (tcache_shutting_down)
return;
/* 這部分代碼整體看就是給 tcache 分配相應的內存,*/
/* 這里涉及到分配區和多線程鎖控制等操作,比較復雜,就不再詳細描述*/
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
if (!victim && ar_ptr != NULL)
{
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
/* In a low memory situation, we may not be able to allocate memory
- in which case, we just keep trying later. However, we
typically do this very early, so either there is sufficient
memory, or there isn't enough memory to do non-trivial
allocations anyway. */
/* tcache 分配好后,將 tcache 處的內存初始化為 0 */
if (victim)
{
tcache = (tcache_perthread_struct *) victim;
memset (tcache, 0, sizeof (tcache_perthread_struct));
}
}
tcache_thread_shutdown (void)
static void tcache_thread_shutdown (void)
{
int i;
tcache_perthread_struct *tcache_tmp = tcache;
/* tcahce 不存在的情況下直接返回 */
if (!tcache)
return;
/* Disable the tcache and prevent it from being reinitialized. */
/* 禁用tcache,防止它被重新初始化 */
tcache = NULL;
tcache_shutting_down = true; /* tcache_shutting_down 的值默認值為 faluse */
/* Free all of the entries and the tcache itself back to the arena
heap for coalescing. */
/* 釋放所有的 tcache ,以便進行合並 */
/* 外層 for 循環遍歷 tcache 指針數組,數組的每個下標對應不同大小的 tcache */
for (i = 0; i < TCACHE_MAX_BINS; ++i)
{
/* 內層 while 循環遍歷整個 tcache 點鏈表,也就是相同大小的 tcache */
while (tcache_tmp->entries[i])
{
/* 依次釋放鏈表上的 tcache */
tcache_entry *e = tcache_tmp->entries[i];
tcache_tmp->entries[i] = e->next;
__libc_free (e);
}
}
/* 將管理 tcache 的結構體也釋放掉 */
__libc_free (tcache_tmp);
}
tcache_get (size_t tc_idx)
static __always_inline void *tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx]; /* 將 idx 對應的 tcache bins 中的第一個 chunk 取出*/
tcache->entries[tc_idx] = e->next; /* 將取出 chunk 的下一個 chunk 做為 tcache bins 中的第一個 chunk */
--(tcache->counts[tc_idx]); /* idx 對應的 tcache bins 中的 chunk 數量減1 */
e->key = NULL;
return (void *) e;
}
_int_malloc (mstate av, size_t bytes)
static void *
_int_malloc (mstate av, size_t bytes)
{
INTERNAL_SIZE_T nb; /* normalized request size */
unsigned int idx; /* associated bin index */
mbinptr bin; /* associated bin *//* mbinptr 和 mchunkptr 類型都是 malloc_chunk 結構體 */
mchunkptr victim; /* inspected/selected chunk */
INTERNAL_SIZE_T size; /* its size */
int victim_index; /* its bin index */
mchunkptr remainder; /* remainder from a split */
unsigned long remainder_size; /* its size */
unsigned int block; /* bit map traverser */
unsigned int bit; /* bit map traverser */
unsigned int map; /* current word of binmap */
mchunkptr fwd; /* misc temp for linking */
mchunkptr bck; /* misc temp for linking */
#if USE_TCACHE
size_t tcache_unsorted_count; /* count of unsorted chunks processed */
#endif
/*
Convert request size to internal form by adding SIZE_SZ bytes
overhead plus possibly more to obtain necessary alignment and/or
to obtain a size of at least MINSIZE, the smallest allocatable
size. Also, checked_request2size returns false for request sizes
that are so large that they wrap around zero when padded and
aligned.
*/
if (!checked_request2size (bytes, &nb))
{
__set_errno (ENOMEM);
return NULL;
}
/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
mmap. */
/* 如果沒有可用的分配區,則調用 sysmalloc 獲取 chunk */
if (__glibc_unlikely (av == NULL))
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
/*
If the size qualifies as a fastbin, first check corresponding bin.
This code is safe to execute even if av is not yet initialized, so we
can try it without checking, which saves some time on this fast path.
*/
/* 從 fast bins 中分配一個 chunk 相當簡單,首先根據所需 chunk 的大小獲得 */
/* 該 chunk 所屬 fast bin 的 index,根據該 index 獲得所需 fast bin 的空 */
/* 閑 chunk 鏈表的頭指針,然后將頭指針的下一個 chunk 作為空閑 chunk 鏈表的頭部。 */
/* 如果使用了 tcache 機制且 fast bin 鏈表中還有 chunk,則將 chunk 放入 tcache bin 中。*/
/* 為了加快從 fast bins 中分配 chunk,處於 fast bins 中 chunk 的狀態仍然保持為 inuse 狀態,*/
/* 避免被相鄰的空閑 chunk 合並,從 fast bins 中分配 chunk,只需取出第一個 chunk,*/
/* 並調用 chunk2mem() 函數返回用戶所需的內存塊 */
#define REMOVE_FB(fb, victim, pp) \
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) \
!= victim); \
/* 所需的 chunk 大小小於等於 fast bins 中的最大 chunk 大小,*/
/* 首先嘗試從 fast bins 中分配 chunk */
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb); /* 獲得與所需內存分配大小的一致的 fast bin 的下標 */
mfastbinptr *fb = &fastbin (av, idx); /* 獲得對應 fast bin 中第一個 chunk */
mchunkptr pp;
victim = *fb;
if (victim != NULL)
{
if (SINGLE_THREAD_P)
/* 不開啟優化算法 */
*fb = victim->fd; /* 獲得對應 fast bin 中第二個 chunk */
else
/* 開啟優化算法 */
REMOVE_FB (fb, pp, victim); /* 原子操作優化算法我們不管它 */
if (__glibc_likely (victim != NULL))
{
size_t victim_idx = fastbin_index (chunksize (victim)); /* 計算之前第一個獲得的 chunk 對應的 fast bin 下標 */
/* 這里進行了檢測,保證用所需分配內存大小取出的 chunk 計算出的 */
/* idx 與使用第一個獲得的 chunk 計算出的 idx 要相等 */
if (__builtin_expect (victim_idx != idx, 0))
malloc_printerr ("malloc(): memory corruption (fast)");
check_remalloced_chunk (av, victim, nb);
#if USE_TCACHE
/* 使用 tcache 機制的情況 */
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb); /* 獲得與所需分配內存大小一致的 tcache bin 的下標 */
if (tcache && tc_idx < mp_.tcache_bins) /* tcache 存在且下標值在最大范圍內 */
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL) /* 當對應的 fast bin 中存在 chunk 且 tcache bin 數量沒滿時 */
{
if (SINGLE_THREAD_P) /* 不使用優化算法 */
*fb = tc_victim->fd;
else
{
REMOVE_FB (fb, pp, tc_victim); /* 使用優化算法 */
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx); /* 將 fast bin 中的 chunk 插入 tcache bin 中 */
}
}
#endif
void *p = chunk2mem (victim); /* 轉化為用戶指針 */
alloc_perturb (p, bytes); /* 內存內容初始化 */
return p;
}
}
}
/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
/* 如果分配的 chunk 屬於 small bin,首先查找 chunk 所對應 small bins 數組的 index,然后 */
/* 根據 index 獲得某個 small bin 的空閑 chunk 雙向循環鏈表表頭,然后將最后一個 chunk 賦值 */
/* 給 victim,如果 victim 與表頭相同,表示該鏈表為空,不能從 small bin 的空閑 chunk 鏈表中 */
/* 分配,這里不處理,等后面的步驟來處理。如果 victim 與表頭不同,將 victim 從 small bin 的 */
/* 雙向循環鏈表中取出,設置 victim chunk 的 inuse 標志,該標志處於 victim chunk 的下一個相 */
` /* 鄰 chunk 的 size 字段的第一個 bit。接着判斷當前分配區是否為非主分配區,如果是,將 victim */
/* chunk 的 size 字段中的表示非主分配區的標志 bit 置 1。如果啟用了 tcache 機制,還需要將small */
/* bin 中與 victim chunk 相同大小的其他 chunk 放入 tcache 中最后調用 chunk2mem()函數獲得 chunk */
/* 的實際可用的內存指針,將該內存指針返回給應用層。到此從 small bins 中分配 chunk 的工作完成了,*/
/* 但我們看到,當對應的 small bin 中沒有空閑 chunk,並沒有獲取到 chunk,需要后面的步驟來處理 */
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb); /* 獲得對應 smallbin 的下標 */
bin = bin_at (av, idx); /* 獲得對應的 chunk 指針 */
if ((victim = last (bin)) != bin) /* 如果 bin 鏈表不為空 */
{
bck = victim->bk;
/* victim 后一個 chunk 的 fd 指針如果指向的不是 victim 就報錯 */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb); /* 設置 inuse 標志 */
/* 將 victim 從雙向鏈表中移除 */
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
set_non_main_arena (victim); /* 設置標志 */
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb); /* 計算 idx */
if (tcache && tc_idx < mp_.tcache_bins) /* 如果 tcache 存在且 idx 沒有超出范圍 */
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin) /* 當 tcache 未滿且 small bin 中還有 chunk */
{
if (tc_victim != 0)
/* 將 chunk 中 small bin 中取出,設置標志位,並放入 tcache 中 */
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim); /* chunk 指針轉為內存指針 */
alloc_perturb (p, bytes);
return p; /* 將內存指針返回 */
}
}
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/
else /* 所需 chunk 屬於 large bin */
{
idx = largebin_index (nb);
if (atomic_load_relaxed (&av->have_fastchunks)) /* 判斷當前分配區的 fast bin 中是否包含 chunk */
malloc_consolidate (av); /* 調用 malloc_consolidate() 函數合並 fast bin 中 的 chunk */
}
/*
Process recently freed or remaindered chunks, taking one only if
it is exact fit, or, if this a small request, the chunk is remainder from
the most recent non-exact fit. Place other traversed chunks in
bins. Note that this step is the only place in any routine where
chunks are placed in bins.
The outer loop here is needed because we might not realize until
near the end of malloc that we should have consolidated, so must
do so and retry. This happens at most once, and only when we would
otherwise need to expand memory to service a "small" request.
*/
#if USE_TCACHE /* 使用 tcache 機制的情況 */
INTERNAL_SIZE_T tcache_nb = 0;
size_t tc_idx = csize2tidx (nb); /* 計算下標 */
if (tcache && tc_idx < mp_.tcache_bins) /* tcache 存在且下標 idx 在范圍內 */
tcache_nb = nb;
int return_cached = 0;
tcache_unsorted_count = 0;
#endif
for (;; )
{
int iters = 0;
/* 反向遍歷 unsorted bin 的雙向循環鏈表,遍歷結束的條件是循環鏈表中只剩下一個頭結點 */
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
size = chunksize (victim); /* 計算 size */
mchunkptr next = chunk_at_offset (victim, size); /* 獲得指向內存空間中當前 chunk 的下一個chunk 的指針 */
/* 檢查當前遍歷的 chunk 是否合法,chunk 的大小不能小於等於 */
/* 2 * SIZE_SZ,也不能超過該分配區總的內存分配量 */
if (__glibc_unlikely (size <= 2 * SIZE_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
/* 對 next chunk 也進行一樣的檢查 */
if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
/* 如果 next chunk 中記錄前一個 chunk 大小的 prev_size 與 size 不符,則報錯 */
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
/* 如果 victim 中 bk 指針指向的 chunk 的 fd 指針不是指向 victim */
/* 或者 victim 的 fd 指針不是指向 bin 頭結點,則報錯 */
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
/* 如果 next chunk 中的顯示前一個 chunk 是否正在使用的標志位為1,*/
/* 即前一個 chunk 正在使用,則報錯 */
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");
/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
/* 如果需要分配一個 small bin chunk,在之前的 small bins 中沒有匹配到合適的 */
/* chunk,並且 unsorted bin 中只有一個 chunk,並且這個 chunk 為 last remainder chunk,*/
/* 並且這個 chunk 的大小大於所需 chunk 的大小加上 MINSIZE,在滿足這些條件的情況下,可以使 */
/* 用這個chunk切分出需要的small bin chunk。這是唯一的從unsorted bin中分配small bin chunk */
/* 的情況,這種優化利於 cpu 的高速緩存命中 */
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
/* 從該 chunk 中切分出所需大小的 chunk,計算切分后剩下 chunk 的大小,*/
/* 將剩下的 chunk 加入 unsorted bin 的鏈表中,並將剩下的 chunk 作為 */
/* 分配區的 last remainder chunk,若剩下的 chunk 屬於 large bin chunk,*/
/* 將該 chunk 的 fd_nextsize 和 bk_nextsize 設置為 NULL,因為這個 chunk */
/* 僅僅存在於 unsorted bin 中,並且 unsorted bin 中有且僅有這一個 chunk */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
/* 設置分配出的 chunk 和 last remainder chunk 的相關信息,如 chunk 的 size,*/
/* 狀態標志位,對於 last remainder chunk,需要調用 set_foot 宏,因為只有處於 */
/* 空閑狀態的 chunk 的 foot 信息(prev_size)才是有效的,處於 inuse 狀態的 chunk */
/* 的 foot 無效,該 foot 是返回給應用層的內存塊的一部分。設置完成 chunk 的相關信息,*/
/* 調用 chunk2mem()獲得 chunk 中可用的內存指針,返回給應用層,退出*/
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim)) /* 這里再次檢查了 bck—>fd == victim */
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
/* 將雙向循環鏈表中的最后一個 chunk 移除 */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
/* Take now instead of binning if exact fit */
/* 如果當前遍歷的 chunk 與所需的 chunk 大小一致,將當前 chunk 返回。首先設置當前 */
/* chunk 處於 inuse 狀態,該標志位處於相鄰的下一個 chunk 的 size 中,如果當前分配區不是 */
/* 主分配區,設置當前 chunk 的非主分配區標志位。如果使用了 tcache 機制,先將 chunk 放入 */
/* tcache 中,待后續處理,否則調用 chunk2mem()獲得 chunk 中可用的內存指針,返回給應用層,退出 */
if (size == nb) /* 如果當前遍歷的 chunk 與所需的 chunk 大小一致 */
{
/* 設置標志 */
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
#if USE_TCACHE /* 使用 tcache 機制情況 */
/* Fill cache first, return to user only if cache fills.
We may return one of these chunks later. */
if (tcache_nb
&& tcache->counts[tc_idx] < mp_.tcache_count) /* tcache 未滿 */
{
tcache_put (victim, tc_idx); /* 將 victim 放入 tcache 中 */
return_cached = 1;
continue;
}
else
{
#endif
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p; /* 返回內存指針 */
#if USE_TCACHE
}
#endif
}
/* place chunk in bin */
/* 如果當前 chunk 屬於 small bins,獲得當前 chunk 所屬 small bin 的 index,*/
/* 並將該 small bin 的鏈表表頭賦值給 bck,第一個 chunk 賦值給 fwd,之后會把當前的 */
/* chunk 會插入到 bck 和 fwd 之間,作為 small bin 鏈表的第一個 chunk */
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
/* 如果當前 chunk 屬於 large bins,獲得當前 chunk 所屬 large bin 的 index,*/
/* 並將該 large bin 的鏈表表頭賦值給 bck,第一個 chunk 賦值給 fwd,之后會把當前的 */
/* chunk 會插入到 bck 和 fwd 之間,作為 large bin 鏈表的第一個 chunk */
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
/* maintain large bins in sorted order */
/* 如果 fwd 不等於 bck,意味着 large bin 中有空閑 chunk存在,由於 large bin 中 */
/* 的空閑 chunk是按照大小順序排序的,需要將當前從 unsorted bin 中取出的 chunk 插 */
/* 入到 large bin 中合適的位置。將當前 chunk 的 size 的 inuse 標志 bit 置位,相 */
/* 當於加 1,便於加快 chunk 大小的比較,找到合適的地方插入當前 chunk。這里還做了一 */
/* 次檢查,斷言在 large bin 雙向循環鏈表中的最后一個 chunk 的 size 字段中的非主分 */
/* 配區的標志 bit 沒有置位,因為所有在 large bin中的 chunk 都處於空閑狀態,該標志位一定是清零的 */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
/* 如果當前chunk比large bin的最后一個chunk的大小還小,那么當前chunk就插入到large */
/* bin 的鏈表的最后,作為最后一個 chunk。可以看出 large bin 中的 chunk 是按照從大到小的 */
/* 順序排序的,同時一個 chunk 存在於兩個雙向循環鏈表中,一個鏈表包含了 large bin 中所有 */
/* 的 chunk,另一個鏈表為 chunk size 鏈表,該鏈表從每個相同大小的 chunk 的取出第一個 chunk */
/* 按照大小順序鏈接在一起,便於一次跨域多個相同大小的 chunk 遍歷下一個不同大小的 */
/* chunk,這樣可以加快在 large bin 鏈表中的遍歷速度 */
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert (chunk_main_arena (fwd));
/* 正向遍歷 chunk size 鏈表,直到找到第一個 chunk 大小小於 */
/* 等於當前 chunk 大小的 chunk退出循環 */
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
/* 如果從 large bin 鏈表中找到了與當前 chunk 大小相同的 chunk,*/
/* 則同一大小的 chunk 已經存在,那么 chunk size 鏈表中一定包含了 fwd */
/* 所指向的 chunk,為了不修改 chunk size 鏈表,當前 chunk 只能插入 fwd 之后 */
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
/* 如果 chunk size 鏈表中還沒有包含當前 chunk 大小的 chunk,也就是說當前 chunk 的大 */
/* 小大於 fwd 的大小,則將當前 chunk 作為該 chunk size 的代表加入 chunk size 鏈表,*/
/* chunk size 鏈表也是按照由大到小的順序排序 */
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
/* 如果 large bin 鏈表中沒有 chunk,直接將當前 chunk 加入 chunk size 鏈表 */
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
#if USE_TCACHE
/* If we've processed as many chunks as we're allowed while
filling the cache, return one of the cached ones. */
++tcache_unsorted_count;
if (return_cached
&& mp_.tcache_unsorted_limit > 0
&& tcache_unsorted_count > mp_.tcache_unsorted_limit)
{
return tcache_get (tc_idx);
}
#endif
#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}
#if USE_TCACHE
/* If all the small chunks we found ended up cached, return one now. */
if (return_cached)
{
return tcache_get (tc_idx);
}
#endif
/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);
/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin
&& (unsigned long) chunksize_nomask (victim)
>= (unsigned long) (nb))
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin)
&& chunksize_nomask (victim)
== chunksize_nomask (victim->fd))
victim = victim->fd;
remainder_size = size - nb;
unlink_chunk (av, victim);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks");
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
/*
Search for a chunk by scanning bins, starting with next largest
bin. This search is strictly by best-fit; i.e., the smallest
(with ties going to approximately the least recently used) chunk
that fits is selected.
The bitmap avoids needing to check that most blocks are nonempty.
The particular case of skipping all bins during warm-up phases
when no chunks have been returned yet is faster than it might look.
*/
++idx;
bin = bin_at (av, idx);
block = idx2block (idx);
map = av->binmap[block];
bit = idx2bit (idx);
for (;; )
{
/* Skip rest of block if there are no more set bits in this block. */
if (bit > map || bit == 0)
{
do
{
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
}
while ((map = av->binmap[block]) == 0);
bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}
/* Advance to bin with set bit. There must be one. */
while ((bit & map) == 0)
{
bin = next_bin (bin);
bit <<= 1;
assert (bit != 0);
}
/* Inspect the bin. It is likely to be non-empty */
victim = last (bin);
/* If a false alarm (empty bin), clear the bit. */
if (victim == bin)
{
av->binmap[block] = map &= ~bit; /* Write through */
bin = next_bin (bin);
bit <<= 1;
}
else
{
size = chunksize (victim);
/* We know the first chunk in this bin is big enough to use. */
assert ((unsigned long) (size) >= (unsigned long) (nb));
remainder_size = size - nb;
/* unlink */
unlink_chunk (av, victim);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks 2");
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
/* advertise as last remainder */
if (in_smallbin_range (nb))
av->last_remainder = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
use_top:
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).
We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/
victim = av->top;
size = chunksize (victim);
if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (atomic_load_relaxed (&av->have_fastchunks))
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}
/*
Otherwise, relay to handle system-dependent cases
*/
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}
}
sysmalloc (INTERNAL_SIZE_T nb, mstate av)
static void *sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{
mchunkptr old_top; /* incoming value of av->top */
INTERNAL_SIZE_T old_size; /* its size */
char *old_end; /* its end address */
long size; /* arg to first MORECORE or mmap call */
char *brk; /* return value from MORECORE */
long correction; /* arg to 2nd MORECORE call */
char *snd_brk; /* 2nd return val */
INTERNAL_SIZE_T front_misalign; /* unusable bytes at front of new space */
INTERNAL_SIZE_T end_misalign; /* partial page left at end of new space */
char *aligned_brk; /* aligned offset into brk */
mchunkptr p; /* the allocated/returned chunk */
mchunkptr remainder; /* remainder from allocation */
unsigned long remainder_size; /* its size */
size_t pagesize = GLRO (dl_pagesize);
bool tried_mmap = false;
/*
If have mmap, and the request size meets the mmap threshold, and
the system supports mmap, and there are few enough currently
allocated mmapped regions, try to directly map this request
rather than expanding top.
*/
/* 如果沒有分配區,或者所需分配的 chunk 大小大於 mmap 分配閾值,默認為 128K,並且當前進程 */
/* 使用 mmap() 分配的內存塊小於設定的最大值,將使用 mmap() 系統調用直接向操作系統申請內存 */
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
char *mm; /* return value from mmap call*/
try_mmap:
/*
Round up size to nearest page. For mmapped chunks, the overhead
is one SIZE_SZ unit larger than for normal chunks, because there
is no following chunk whose prev_size field could be used.
See the front_misalign handling below, for glibc there is no
need for further alignments unless we have have high alignment.
*/
/* 計算滿足頁對齊的最小分配內存大小 */
if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
size = ALIGN_UP (nb + SIZE_SZ, pagesize);
else
size = ALIGN_UP (nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
tried_mmap = true;
/* Don't try if size wraps around 0 */
/* 如果重新計算所需分配的 size 小於 nb,表示溢出了,*/
/* 不分配內存,否則,調用 mmap()分配所需大小的內存 */
if ((unsigned long) (size) > (unsigned long) (nb))
{
mm = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));
/* 如果 mmap()分配內存成功,將 mmap()返回的內存指針強制轉換為chunk 指針,*/
/* 並設置該 chunk 的大小為 size,同時設置該 chunk 的 IS_MMAPPED 標志位,*/
/* 表示本 chunk 是通過 mmap()函數直接從系統分配的 */
if (mm != MAP_FAILED)
{
/*
The offset to the start of the mmapped region is stored
in the prev_size field of the chunk. This allows us to adjust
returned start address to meet alignment requirements here
and in memalign(), and still be able to compute proper
address argument for later munmap in free() and realloc().
*/
if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
{
/* For glibc, chunk2mem increases the address by 2*SIZE_SZ and
MALLOC_ALIGN_MASK is 2*SIZE_SZ-1. Each mmap'ed area is page
aligned and therefore definitely MALLOC_ALIGN_MASK-aligned. */
/* 檢測是否對齊 */
assert (((INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK) == 0);
front_misalign = 0;
}
else
front_misalign = (INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK;
/* 如果 MALLOC_ALIGNMENT 不等於 2 * SIZE_SZ,則分配的內存 */
/* 可能是不對齊的,按照 MALLOC_ALIGNMENT 具體的值進行對齊 */
if (front_misalign > 0)
{
correction = MALLOC_ALIGNMENT - front_misalign;
p = (mchunkptr) (mm + correction);
set_prev_size (p, correction);
set_head (p, (size - correction) | IS_MMAPPED);
}
else
{
p = (mchunkptr) mm;
set_prev_size (p, 0);
set_head (p, size | IS_MMAPPED);
}
/* update statistics */
/* 更新相關統計值,首先將當前進程 mmap 分配內存塊的計數加一,如果使用 */
/* mmap() 分配的內存塊數量大於設置的最大值,將最大值設置為最新值,這個 */
/* 判斷不會成功,因為使用mmap分配內存的條件中包括了mp_.n_mmaps < mp_.n_mmaps_max,*/
/* 所以++mp_.n_mmaps > mp_.max_n_mmaps 不會成立。然后更新 mmap 分配的內存 */
/* 總量,如果該值大於設置的最大值,將當前值賦值給 mp_.max_mmapped_mem */
int new = atomic_exchange_and_add (&mp_.n_mmaps, 1) + 1;
atomic_max (&mp_.max_n_mmaps, new);
unsigned long sum;
sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size;
atomic_max (&mp_.max_mmapped_mem, sum);
check_chunk (av, p);
/* 將分配的 chunk 的指針返回,用戶存放數據時就是從該指針指向的內存開始存放 */
return chunk2mem (p);
}
}
}
/* There are no usable arenas and mmap also failed. */
if (av == NULL)
return 0;
/* Record incoming configuration of top */
/* 保存當前 top chunk 的指針,大小和結束地址到臨時變量中 */
old_top = av->top;
old_size = chunksize (old_top);
old_end = (char *) (chunk_at_offset (old_top, old_size));
brk = snd_brk = (char *) (MORECORE_FAILURE);
/*
If not the first time through, we require old_size to be
at least MINSIZE and to have prev_inuse set.
*/
/* 檢查 top chunk 的合法性,如果第一次調用本函數,top chunk 可能沒有初始化,*/
/* 可能 old_size 為 0,如果 top chunk 已經初始化,則 top chunk 的大小必須 */
/* 大於等於 MINSIZE,因為 top chunk 中包含了 fencepost,fencepost 需要 MINSIZE */
/* 大小的內存。Top chunk 必須標識前一個 chunk 處於 inuse 狀態,這是規定,並且 */
/* top chunk 的結束地址必定是頁對齊的。另外 top chunk 的除去 fencepost 的大小 */
/* 必定小於所需 chunk 的大小,不然在_int_malloc()函數中就應該使用 top chunk 獲得所需的 chunk */
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
/* 如果當前分配區為非主分配區*/
if (av != &main_arena)
{
/* 這里的 heap_info 是 _heap_info 結構體 */
heap_info *old_heap, *heap;
size_t old_heap_size;
/* First try to extend the current heap. */
old_heap = heap_for_ptr (old_top);
old_heap_size = old_heap->size;
/* 根據 top chunk 的指針獲得當前 sub_heap 的 heap_info 實例,*/
/* 如果 top chunk 的剩余有效空間不足以分配出所需的 chunk(前 */
/* 面已經斷言,這個肯定成立),嘗試增長 sub_heap 的可讀可寫區 */
/* 域大小,如果成功,修改過內存分配的統計信息,並更新新的 top chunk 的 size */
if ((long) (MINSIZE + nb - old_size) > 0
&& grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
{
av->system_mem += old_heap->size - old_heap_size;
set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
| PREV_INUSE);
}
/* 調用 new_heap()函數創建一個新的 sub_heap,由於這個 sub_heap 中至少 */
/* 需要容下大小為 nb 的 chunk,大小為 MINSIZE 的 fencepost 和大小為 sizeof(*heap) */
/* 的 heap_info 實例,所以傳入 new_heap()函數的分配大小為 nb + (MINSIZE + sizeof(*heap)) */
else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
{
/* 使新創建的 sub_heap 保存當前的分配區指針,將該 sub_heap 加入當前分配區的 */
/* sub_heap 鏈表中,更新當前分配區內存分配統計,將新創建的 sub_heap 僅有的一 */
/* 個空閑chunk 作為當前分配區的 top chunk,並設置 top chunk 的狀態 */
/* Use a newly allocated heap. */
heap->ar_ptr = av;
heap->prev = old_heap;
av->system_mem += heap->size;
/* Set up the new top. */
top (av) = chunk_at_offset (heap, sizeof (*heap));
set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);
/* Setup fencepost and free the old top chunk with a multiple of
MALLOC_ALIGNMENT in size. */
/* The fencepost takes at least MINSIZE bytes, because it might
become the top chunk again later. Note that a footer is set
up, too, although the chunk is marked in use. */
old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
/* 設置原 top chunk 的 fencepost,fencepost 需要 MINSIZE 大小的內存空間,將該 old_size */
/* 減去 MINSIZE 得到原 top chunk 的有效內存空間,首先設置 fencepost 的第二個 chunk 的 size */
/* 為 0,並標識前一個 chunk 處於 inuse 狀態。接着判斷原 top chunk 的有效內存空間上是否大 */
/* 於等於 MINSIZE,如果是,表示原 top chunk 可以分配出大於等於 MINSIZE 大小的 chunk,於 */
/* 是將原 top chunk 切分成空閑 chunk 和 fencepost 兩部分,先設置 fencepost 的第一個 chunk */
/* 的大小為 2*SIZE_SZ,並標識前一個 chunk 處於 inuse 狀態,fencepost 的第一個 chunk 還需 */
/* 要設置 foot,表示該 chunk 處於空閑狀態,而 fencepost 的第二個 chunk 卻標識第一個 chunk */
/* 處於 inuse 狀態,因為不能有兩個空閑 chunk 相鄰,才會出現這么奇怪的 fencepost。另外其 */
/* 實 top chunk 切分出來的 chunk 也是處於空閑狀態,但 fencepost 的第一個 chunk 卻標識前一 */
/* 個 chunk 為 inuse 狀態,然后強制將該處於 inuse 狀態的 chunk 調用_int_free()函數釋放掉 */
/* 這樣做完全是要遵循不能有兩個空閑 chunk 相鄰的約定 */
if (old_size >= MINSIZE)
{
set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
_int_free (av, old_top, 1);
}
/* 如果原 top chunk 中有效空間不足 MINSIZE,則將整個原 top chunk 作為 fencepost,*/
/* 並設置 fencepost 的第一個 chunk 的相關狀態 */
else
{
set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
set_foot (old_top, (old_size + 2 * SIZE_SZ));
}
}
/* 如果增長 sub_heap 的可讀可寫區域大小和創建新 sub_heap 都失敗了,*/
/* 嘗試使用 mmap() 函數直接從系統分配所需 chunk */
else if (!tried_mmap)
/* We can at least try to use to mmap memory. */
goto try_mmap;
}
else /* av == main_arena */
/* 如果當前分配區為主分配區 */
{ /* Request enough space for nb + pad + overhead */
size = nb + mp_.top_pad + MINSIZE;
/*
If contiguous, we can subtract out existing space that we hope to
combine with new space. We add it back later only if
we don't actually get contiguous space.
*/
/* 一般情況下,主分配區使用 sbrk()從 heap 中分配內存,sbrk()返回連續的虛擬內存,*/
/* 這里調整需要分配的 size,減掉 top chunk 中已有空閑內存大小 */
if (contiguous (av))
size -= old_size;
/*
Round to a multiple of page size.
If MORECORE is not contiguous, this ensures that we only call it
with whole-page arguments. And if MORECORE is contiguous and
this is not first time through, this preserves page-alignment of
previous calls. Otherwise, we correct to page-align below.
*/
/* 將 size 按照頁對齊,sbrk()必須以頁為單位分配連續虛擬內存 */
size = ALIGN_UP (size, pagesize);
/*
Don't try to call MORECORE if argument is so big as to appear
negative. Note that since mmap takes size_t arg, it may succeed
below even if we cannot call MORECORE.
*/
/* 使用 sbrk()從 heap 中分配 size 大小的虛擬內存塊 */
if (size > 0)
{
brk = (char *) (MORECORE (size));
LIBC_PROBE (memory_sbrk_more, 2, brk, size);
}
/* 如果 sbrk()分配成功,並且 morecore 的 hook 函數存在,調用 morecore 的 hook 函數 */
if (brk != (char *) (MORECORE_FAILURE))
{
/* Call the `morecore' hook if necessary. */
void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
if (__builtin_expect (hook != NULL, 0))
(*hook)();
}
else
{
/*
If have mmap, try using it as a backup when MORECORE fails or
cannot be used. This is worth doing on systems that have "holes" in
address space, so sbrk cannot extend to give contiguous space, but
space is available elsewhere. Note that we ignore mmap max count
and threshold limits, since the space will not be used as a
segregated mmap region.
*/
/* 如果 sbrk()返回失敗,或是 sbrk()不可用,使用 mmap()代替,重新計算所需分配的內存 */
/* 大小並按頁對齊,如果重新計算的 size 小於 1M,將 size 設為 1M,也就是說使用 mmap() */
/* 作為 morecore 函數分配的最小內存塊大小為 1M */
/* Cannot merge with old top, so add its size back in */
if (contiguous (av))
size = ALIGN_UP (size + old_size, pagesize);
/* If we are relying on mmap as backup, then use larger units */
if ((unsigned long) (size) < (unsigned long) (MMAP_AS_MORECORE_SIZE))
size = MMAP_AS_MORECORE_SIZE;
/* Don't try if size wraps around 0 */
/* 如果所需分配的內存大小合法,使用 mmap()函數分配內存。如果分配成功,更新 brk */
/* 和 snd_brk,並將當前分配區屬性設置為可分配不連續虛擬內存塊 */
if ((unsigned long) (size) > (unsigned long) (nb))
{
char *mbrk = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));
if (mbrk != MAP_FAILED)
{
/* We do not need, and cannot use, another sbrk call to find end */
brk = mbrk;
snd_brk = brk + size;
/*
Record that we no longer have a contiguous sbrk region.
After the first time mmap is used as backup, we do not
ever rely on contiguous space since this could incorrectly
bridge regions.
*/
set_noncontiguous (av);
}
}
}
/* 如果brk合法,即sbrk()或mmap()分配成功,如果sbrk_base還沒有初始化,*/
/* 更新 sbrk_base 和當前分配區的內存分配總量 */
if (brk != (char *) (MORECORE_FAILURE))
{
if (mp_.sbrk_base == 0)
mp_.sbrk_base = brk;
av->system_mem += size;
/*
If MORECORE extends previous space, we can likewise extend top size.
*/
/* 如果 sbrk()分配成功,更新 top chunk 的大小,並設定 top chunk 的前一個 chunk 處於 inuse */
/* 狀態。如果當前分配區可分配連續虛擬內存,原 top chunk 的大小大於 0,但新的 brk 值小 */
/* 於原 top chunk 的結束地址,則出錯了 */
if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE))
set_head (old_top, (size + old_size) | PREV_INUSE);
else if (contiguous (av) && old_size && brk < old_end)
/* Oops! Someone else killed our space.. Can't touch anything. */
malloc_printerr ("break adjusted to free malloc space");
/*
Otherwise, make adjustments:
* If the first time through or noncontiguous, we need to call sbrk
just to find out where the end of memory lies.
* We need to ensure that all returned chunks from malloc will meet
MALLOC_ALIGNMENT
* If there was an intervening foreign sbrk, we need to adjust sbrk
request size to account for fact that we will not be able to
combine new space with existing space in old_top.
* Almost all systems internally allocate whole pages at a time, in
which case we might as well use the whole last page of request.
So we allocate enough more memory to hit a page boundary now,
which in turn causes future contiguous calls to page-align.
*/
/* 執行到這個分支,意味着 sbrk()返回的 brk 值大於原 top chunk 的結束地址,*/
/* 那么新的地址與原 top chunk 的地址不連續,可能是由於外部其它地方調用 sbrk()函數,*/
/* 這里需要處理地址的重新對齊問題 */
else
{
front_misalign = 0;
end_misalign = 0;
correction = 0;
aligned_brk = brk;
/* handle contiguous cases */
if (contiguous (av))
{
/* Count foreign sbrk as system_mem. */
/* 如果本分配區可分配連續虛擬內存,並且有外部調用了 sbrk()函數,*/
/* 將外部調用 sbrk()分配的內存計入當前分配區所分配內存統計中 */
if (old_size)
av->system_mem += brk - old_end;
/* Guarantee alignment of first new chunk made from this space */
/* 計算當前的 brk 要矯正的字節數據,保證 brk 地址按 MALLOC_ALIGNMENT 對齊 */
front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
if (front_misalign > 0)
{
/*
Skip over some bytes to arrive at an aligned position.
We don't need to specially mark these wasted front bytes.
They will never be accessed anyway because
prev_inuse of av->top (and any chunk created from its start)
is always true after initialization.
*/
correction = MALLOC_ALIGNMENT - front_misalign;
aligned_brk += correction;
}
/*
If this isn't adjacent to existing space, then we will not
be able to merge with old_top space, so must add to 2nd request.
*/
/* 由於原 top chunk 的地址與當前 brk 不相鄰,也就不能再使用原 top chunk 的內存了,需 */
/* 要重新為所需 chunk 分配足夠的內存,將原 top chunk 的大小加到矯正值中,從當前 brk 中 */
/* 分配所需 chunk,計算出未對齊的 chunk 結束地址 end_misalign,然后將 end_misalign 按照 */
/* 頁對齊計算出需要矯正的字節數加到矯正值上。然后再調用 sbrk()分配矯正值大小的內存,*/
/* 如果 sbrk()分配成功,則當前的 top chunk 中可以分配出所需的連續內存的 chunk */
correction += old_size;
/* Extend the end address to hit a page boundary */
end_misalign = (INTERNAL_SIZE_T) (brk + size + correction);
correction += (ALIGN_UP (end_misalign, pagesize)) - end_misalign;
assert (correction >= 0);
snd_brk = (char *) (MORECORE (correction));
/*
If can't allocate correction, try to at least find out current
brk. It might be enough to proceed without failing.
Note that if second sbrk did NOT fail, we assume that space
is contiguous with first sbrk. This is a safe assumption unless
program is multithreaded but doesn't use locks and a foreign sbrk
occurred between our first and second calls.
*/
/* 如果 sbrk()執行失敗,更新當前 brk 的結束地址 */
if (snd_brk == (char *) (MORECORE_FAILURE))
{
correction = 0;
snd_brk = (char *) (MORECORE (0));
}
else
/* 如果 sbrk()執行成功,並且有 morecore hook 函數存在,執行該 hook 函數 */
{
/* Call the `morecore' hook if necessary. */
void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
if (__builtin_expect (hook != NULL, 0))
(*hook)();
}
}
/* handle non-contiguous cases */
else
/* 執行到這里,意味着 brk 是用 mmap()分配的 */
{
if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
/* MORECORE/mmap must correctly align */
assert (((unsigned long) chunk2mem (brk) & MALLOC_ALIGN_MASK) == 0);
else
/* 對齊操作 */
{
front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
if (front_misalign > 0)
{
/*
Skip over some bytes to arrive at an aligned position.
We don't need to specially mark these wasted front bytes.
They will never be accessed anyway because
prev_inuse of av->top (and any chunk created from its start)
is always true after initialization.
*/
aligned_brk += MALLOC_ALIGNMENT - front_misalign;
}
}
/* Find out current end of memory */
/* 如果 brk 的結束地址非法,使用 morecore 獲得當前 brk 的結束地址 */
if (snd_brk == (char *) (MORECORE_FAILURE))
{
snd_brk = (char *) (MORECORE (0));
}
}
/* Adjust top based on results of second sbrk */
/* 如果 brk 的結束地址合法,設置當前分配區的 top chunk 為 brk,*/
/* 設置 top chunk 的大小,並更新分配區的總分配內存量 */
if (snd_brk != (char *) (MORECORE_FAILURE))
{
av->top = (mchunkptr) aligned_brk;
set_head (av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE);
av->system_mem += correction;
/*
If not the first time through, we either have a
gap due to foreign sbrk or a non-contiguous region. Insert a
double fencepost at old_top to prevent consolidation with space
we don't own. These fenceposts are artificial chunks that are
marked as inuse and are in any case too small to use. We need
two to make sizes and alignments work out.
*/
/* 設置原 top chunk 的 fencepost,fencepost 需要 MINSIZE 大小的內存空間,將該 old_size */
/* 減去 MINSIZE 得到原 top chunk 的有效內存空間,我們可以確信原 top chunk 的有效內存空間 */
/* 一定大於 MINSIZE,將原 top chunk 切分成空閑 chunk 和 fencepost 兩部分,首先設置切分出 */
/* 來的 chunk 的大小為 old_size,並標識前一個 chunk 處於 inuse 狀態,原 top chunk 切分出來 */
/* 的chunk本應處於空閑狀態,但fencepost的第一個chunk卻標識前一個chunk為inuse狀態, */
/* 然后強制將該處於 inuse 狀態的 chunk 調用_int_free()函數釋放掉。然后設置 fencepost 的第 */
/* 一個 chunk 的大小為 2*SIZE_SZ,並標識前一個 chunk 處於 inuse 狀態,然后設置 fencepost */
/* 的第二個 chunk 的 size 為 2*SIZE_SZ,並標識前一個 chunk 處於 inuse 狀態。這里的主分配區 */
/* 的 fencepost 與非主分配區的 fencepost 不同,主分配區 fencepost 的第二個 chunk 的大小設 */
/* 置為 2*SIZE_SZ,而非主分配區的 fencepost 的第二個 chunk 的大小設置為 0 */
if (old_size != 0)
{
/*
Shrink old_top to insert fenceposts, keeping size a
multiple of MALLOC_ALIGNMENT. We know there is at least
enough space in old_top to do this.
*/
old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
set_head (old_top, old_size | PREV_INUSE);
/*
Note that the following assignments completely overwrite
old_top when old_size was previously MINSIZE. This is
intentional. We need the fencepost, even if old_top otherwise gets
lost.
*/
set_head (chunk_at_offset (old_top, old_size),
(2 * SIZE_SZ) | PREV_INUSE);
set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ),
(2 * SIZE_SZ) | PREV_INUSE);
/* If possible, release the rest. */
if (old_size >= MINSIZE)
{
_int_free (av, old_top, 1);
}
}
}
}
}
} /* if (av != &main_arena) */
/* 如果當前分配區所分配的內存量大於設置的最大值,更新當前分配區最大分配的內存量 */
if ((unsigned long) av->system_mem > (unsigned long) (av->max_system_mem))
av->max_system_mem = av->system_mem;
check_malloc_state (av);
/* finally, do the allocation */
p = av->top;
size = chunksize (p);
/* check that one of the above allocation paths succeeded */
/* 如果當前 top chunk 中已經有足夠的內存來分配所需的 chunk,*/
/* 從當前的 top chunk 中分配所需的 chunk 並返回 */
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (p, nb);
av->top = remainder;
set_head (p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, p, nb);
return chunk2mem (p);
}
/* catch all failure paths */
__set_errno (ENOMEM);
return 0;
}
grow_heap (heap_info *h, long diff)
Grow_heap()函數的實現比較簡單,首先將要增加的可讀可寫的內存大小按照頁對齊,然后計算 sub_heap 總的可讀可寫的內存大小 new_size,判斷 new_size 是否大於HEAP_MAX_SIZE,如果是,返回,否則判斷 new_size 是否大於當前 sub_heap 的可讀可寫區域大小,如果否,調用 mprotect()設置新增的區域可讀可寫,並更新當前 sub_heap 的可讀可寫區域的大小為 new_size。最后將當前 sub_heap 的字段 size 更新為 new_size。
static int
grow_heap (heap_info *h, long diff)
{
size_t pagesize = GLRO (dl_pagesize);
long new_size;
diff = ALIGN_UP (diff, pagesize);
new_size = (long) h->size + diff;
if ((unsigned long) new_size > (unsigned long) HEAP_MAX_SIZE)
return -1;
if ((unsigned long) new_size > h->mprotect_size)
{
if (__mprotect ((char *) h + h->mprotect_size,
(unsigned long) new_size - h->mprotect_size,
PROT_READ | PROT_WRITE) != 0)
return -2;
h->mprotect_size = new_size;
}
h->size = new_size;
LIBC_PROBE (memory_heap_more, 2, h, h->size);
return 0;
}
new_heap (size_t size, size_t top_pad)
New_heap() 函數負責從 mmap 區域映射一塊內存來作為 sub_heap,在 32 位系統上,該函數每次映射 1M 內存,映射的內存塊地址按 1M 對齊;在 64 為系統上,該函數映射 64M
內存,映射的內存塊地址按 64M 對齊。New_heap() 函數只是映射一塊虛擬地址空間,該空間不可讀寫,不會被 swap。
static heap_info *new_heap (size_t size, size_t top_pad)
{
size_t pagesize = GLRO (dl_pagesize);
char *p1, *p2;
unsigned long ul;
heap_info *h;
/* 調整 size 的大小,size 的最小值為 32K,最大值 HEAP_MAX_SIZE 在不同 */
/* 的系統上不同,在 32 位系統為 1M,64 位系統為 64M,將 size 的大小調 */
/* 整到最小值與最大值之間,並以頁對齊,如果 size 大於最大值,直接報錯 */
if (size + top_pad < HEAP_MIN_SIZE)
size = HEAP_MIN_SIZE;
else if (size + top_pad <= HEAP_MAX_SIZE)
size += top_pad;
else if (size > HEAP_MAX_SIZE)
return 0;
else
size = HEAP_MAX_SIZE;
size = ALIGN_UP (size, pagesize);
/* A memory region aligned to a multiple of HEAP_MAX_SIZE is needed.
No swap space needs to be reserved for the following large
mapping (on Linux, this is the case for all non-writable mappings
anyway). */
/* 全局變量 aligned_heap_area 是上一次調用 mmap 分配內存的結束虛擬地址,並已經按 */
/* 照 HEAP_MAX_SIZE 大小對齊。如果 aligned_heap_area 不為空,嘗試從上次映射結束 */
/* 地址開始映射大小為 HEAP_MAX_SIZE 的內存塊,由於全局變量 aligned_heap_area 沒 */
/* 有鎖保護,可能存在多個線程同時 mmap()函數從 aligned_heap_area 開始映射新的虛擬 */
/* 內存塊,操作系統會保證只會有一個線程會成功,其它在同一地址映射新虛擬內存塊都會失敗。*/
/* 無論映射是否成功,都將全局變量 aligned_heap_area 設置為 NULL。如果映射成功,但 */
/* 返回的虛擬地址不是按 HEAP_MAX_SIZE 大小對齊的,取消該區域的映射,映射失敗 */
p2 = MAP_FAILED;
if (aligned_heap_area)
{
p2 = (char *) MMAP (aligned_heap_area, HEAP_MAX_SIZE, PROT_NONE,
MAP_NORESERVE);
aligned_heap_area = NULL;
if (p2 != MAP_FAILED && ((unsigned long) p2 & (HEAP_MAX_SIZE - 1)))
{
__munmap (p2, HEAP_MAX_SIZE);
p2 = MAP_FAILED;
}
}
/* 全局變量 aligned_heap_area 是上一次調用 mmap 分配內存的結束虛擬地址,並已經按 */
/* 照 HEAP_MAX_SIZE 大小對齊。如果 aligned_heap_area 不為空,嘗試從上次映射結束 */
/* 地址開始映射大小為 HEAP_MAX_SIZE 的內存塊,由於全局變量 aligned_heap_area 沒有鎖保護,可
能存在多個線程同時 mmap()函數從 aligned_heap_area 開始映射新的虛擬內存塊,操作系統
會保證只會有一個線程會成功,其它在同一地址映射新虛擬內存塊都會失敗。無論映射是否
成功,都將全局變量 aligned_heap_area 設置為 NULL。如果映射成功,但返回的虛擬地址不
是按 HEAP_MAX_SIZE 大小對齊的,取消該區域的映射,映射失敗。*/
if (p2 == MAP_FAILED)
{
p1 = (char *) MMAP (0, HEAP_MAX_SIZE << 1, PROT_NONE, MAP_NORESERVE);
/* 映射 2 倍 HEAP_MAX_SIZE 大小的虛擬內存成功,將大於等於 p1 並按 HEAP_MAX_SIZE */
/* 大小對齊的第一個虛擬地址賦值給 p2,p2 作為 sub_heap 的起始虛擬地址,p2+HEAP_MAX_SIZE */
/* 作為 sub_heap 的結束地址,並將 sub_heap 的結束地址賦值給全局變量aligned_heap_area,*/
/* 最后還需要將多余的虛擬內存還回給操作系統 */
if (p1 != MAP_FAILED)
{
p2 = (char *) (((unsigned long) p1 + (HEAP_MAX_SIZE - 1))
& ~(HEAP_MAX_SIZE - 1));
ul = p2 - p1;
if (ul)
__munmap (p1, ul);
else
aligned_heap_area = p2 + HEAP_MAX_SIZE;
__munmap (p2 + HEAP_MAX_SIZE, HEAP_MAX_SIZE - ul);
}
else
{
/* Try to take the chance that an allocation of only HEAP_MAX_SIZE
is already aligned. */
/* 映射 2 倍 HEAP_MAX_SIZE 大小的虛擬內存失敗了,再嘗試映射 HEAP_MAX_SIZE */
/* 大小的虛擬內存,如果失敗,返回;如果成功,但該虛擬地址不是按照 HEAP_MAX_SIZE */
/* 大小對齊的,返回 */
p2 = (char *) MMAP (0, HEAP_MAX_SIZE, PROT_NONE, MAP_NORESERVE);
if (p2 == MAP_FAILED)
return 0;
if ((unsigned long) p2 & (HEAP_MAX_SIZE - 1))
{
__munmap (p2, HEAP_MAX_SIZE);
return 0;
}
}
}
/* 調用 mprotect()函數將 size 大小的內存設置為可讀可寫,如果失敗,*/
/* 解除整個 sub_heap 的映射。然后更新 heap_info 實例中的相關字段 */
if (__mprotect (p2, size, PROT_READ | PROT_WRITE) != 0)
{
__munmap (p2, HEAP_MAX_SIZE);
return 0;
}
h = (heap_info *) p2;
h->size = size;
h->mprotect_size = size;
LIBC_PROBE (memory_heap_new, 2, h, h->size);
return h;
}
tcache_put (mchunkptr chunk, size_t tc_idx)
static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache; /* 設置 key 的值為 tcache */
/* 將 chunk 插入 tcache bin 中 */
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
malloc_consolidate(mstate av)
static void malloc_consolidate(mstate av)
{
mfastbinptr* fb; /* current fastbin being consolidated */
mfastbinptr* maxfb; /* last fastbin (for loop control) */
mchunkptr p; /* current chunk being consolidated */
mchunkptr nextp; /* next chunk to consolidate */
mchunkptr unsorted_bin; /* bin header */
mchunkptr first_unsorted; /* chunk to link to */
/* These have same use as in free() */
mchunkptr nextchunk;
INTERNAL_SIZE_T size;
INTERNAL_SIZE_T nextsize;
INTERNAL_SIZE_T prevsize;
int nextinuse;
atomic_store_relaxed (&av->have_fastchunks, false);
unsorted_bin = unsorted_chunks(av);
/*
Remove each chunk from fast bin and consolidate it, placing it
then in unsorted bin. Among other reasons for doing this,
placing in unsorted bin avoids needing to calculate actual bins
until malloc is sure that chunks aren't immediately going to be
reused anyway.
*/
maxfb = &fastbin (av, NFASTBINS - 1);
fb = &fastbin (av, 0);
do {
p = atomic_exchange_acq (fb, NULL);
if (p != 0) {
do {
{
unsigned int idx = fastbin_index (chunksize (p));
if ((&fastbin (av, idx)) != fb)
malloc_printerr ("malloc_consolidate(): invalid chunk size");
}
check_inuse_chunk(av, p);
nextp = p->fd;
/* Slightly streamlined version of consolidation code in free() */
size = chunksize (p);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size in fastbins");
unlink_chunk (av, p);
}
if (nextchunk != av->top) {
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
if (!nextinuse) {
size += nextsize;
unlink_chunk (av, nextchunk);
} else
clear_inuse_bit_at_offset(nextchunk, 0);
first_unsorted = unsorted_bin->fd;
unsorted_bin->fd = p;
first_unsorted->bk = p;
if (!in_smallbin_range (size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}
} while ( (p = nextp) != 0);
}
} while (fb++ != maxfb);
}
結構體
tcache_perthread_struct & tcache_entry
/* 管理 tcache 的結構 */
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS]; /* 統計數組中每個下標有多少對應的 chunk ,TCHACHE_MAX_BINS 的值一般是 64 */
tcache_entry *entries[TCACHE_MAX_BINS]; /* 指向不同 tcache 的指針數組*/
} tcache_perthread_struct;
/* tcache 的基本結構,通過單項鏈表連接 */
typedef struct tcache_entry
{
struct tcache_entry *next; /* 指向下一個 tcache 的指針 */
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key; /* 新增的防止 tcache double free 的機制 */
} tcache_entry;
malloc_state
glibc 使用 malloc_state 來管理內存分配區,每個分配區是 struct malloc_state 的一個實例。
struct malloc_state
{
/* Serialize access. */
/* Mutex 用於串行化訪問分配區,當有多個線程訪問同一個分配區時, */
/* 第一個獲得這個 mutex 的線程將使用該分配區分配內存,分配完成后, */
/* 釋放該分配區的 mutex,以便其它線程使用該分配區 */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast). */
/* Flags 記錄了分配區的一些標志 */
int flags;
/* Set if the fastbin chunks contain recently inserted free blocks. */
/* Note this is a bool but not all targets support atomics on booleans. */
/* 標志位,判斷 fastbin 最近是否有插入塊 */
int have_fastchunks;
/* Fastbins */
/* mchunkptr 與 mfastbinptr 類型其實都是 malloc_chunk 結構體*/
/* 用來記錄和管理 fastbin chunk */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
/* top chunk */
mchunkptr top;
/* The remainder from the most recent split of a small request */
/* Last remainder chunk */
mchunkptr last_remainder;
/* Normal bins packed as described above */
/* bins 包括 unsorted chunk、small chunk 和 large chunk */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* Linked list */
/* 指向下一個分配區的指針 */
struct malloc_state *next;
/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
/* 指向下一個空閑的分配區 */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
/* 附加到當前分配區的進程數 */
INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
malloc_chunk
glibc 將內存划分成很多大小不同的塊,從而對內存的分配與回收進行管理。在 glibc 的實現源碼中定義結構體 malloc_chunk 來描述這些塊。
struct malloc_chunk {
/* 如果前一個 chunk 是空閑的,該域表示前一個 chunk 的大小 */
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
/* 當前 chunk 的大小,並且記錄了當前 chunk 和前一個 chunk 的一些屬性 */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
/* 指針 fd 和 bk 只有當該 chunk 塊空閑時才存在,其作用是用 */
/* 於將對應的空閑 chunk 塊加入到空閑 chunk 塊鏈表中統一管理*/
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
/* 當當前的 chunk 存在於 large bins 中時,large bins 中的空閑 chunk */
/* 是按照大小排序的,但同一個大小的 chunk 可能有多個,增加了這兩個字段 */
/* 可以加快遍歷空閑 chunk,並查找滿足需要的空閑 chunk,fd_nextsize 指 */
/* 向下一個比當前 chunk 大小大的第一個空閑 chunk,bk_nextszie 指向前 */
/* 一個比當前 chunk 大小小的第一個空閑 chunk */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
malloc_par
glibc 的參數管理使用 struct malloc_par,全局擁有一個唯一的 malloc_par 實例。
struct malloc_par
{
/* Tunable parameters */
unsigned long trim_threshold; /* top chunk 的收縮閾值 */
INTERNAL_SIZE_T top_pad; /* 在分配內存時是否添加額外的 pad,默認該字段為 0 */
INTERNAL_SIZE_T mmap_threshold; /* mmap 分配閾值 */
INTERNAL_SIZE_T arena_test; /* 當每個進程的分配區數量小於等於 arena_test 時,不會重用已有的分配區 */
INTERNAL_SIZE_T arena_max; /* 當系統中的分配區數量達到 arena_max,就不會再創建新的分配區,只會重用已有的分配區 */
/* Memory map support */
int n_mmaps; /* 當前進程使用 mmap()函數分配的內存塊的個數 */
int n_mmaps_max; /* mmap()函數分配的內存塊的最大數量 */
int max_n_mmaps; /* mmap()函數分配的內存塊的數量的最大值 */
/* the mmap_threshold is dynamic, until the user sets
it manually, at which point we need to disable any
dynamic behavior. */
int no_dyn_threshold; /* 否開啟 mmap 分配閾值動態調整機制,默認值為 0,即開啟 */
/* Statistics */
/* mmapped_mem 和 max_mmapped_mem 都用於統計 mmap 分配的內存大小,一般情況下兩個字段的值相等 */
INTERNAL_SIZE_T mmapped_mem;
INTERNAL_SIZE_T max_mmapped_mem;
/* First address handed out by MORECORE/sbrk. */
char *sbrk_base; /* 堆的起始地址 */
#if USE_TCACHE
/* Maximum number of buckets to use. */
size_t tcache_bins; /* tcache bins 的數量 */
size_t tcache_max_bytes; /* 最大 tache 的大小 */
/* Maximum number of chunks in each bucket. */
size_t tcache_count; /* 每個 tcache bins 中tcaches 的最大數量 */
/* Maximum number of chunks to remove from the unsorted list, which
aren't used to prefill the cache. */
size_t tcache_unsorted_limit;
#endif
};
_heap_info
typedef struct _heap_info
{
/* 指向所屬分配區的指針 */
mstate ar_ptr; /* Arena for this heap. */
/* 用於將同一個分配區中的 sub_heap 用單向鏈表鏈接起來 */
/* prev 指向鏈表中的前一個 sub_heap */
struct _heap_info *prev; /* Previous heap. */
/* 表示當前 sub_heap 中的內存大小,以 page 對齊 */
size_t size; /* Current size in bytes. */
/* 當前 sub_heap 中被讀寫保護的內存大小,*/
/* 也就是說還沒有被分配的內存大小*/
size_t mprotect_size; /* Size in bytes that has been mprotected
PROT_READ|PROT_WRITE. */
/* Make sure the following data is properly aligned, particularly
that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
MALLOC_ALIGNMENT. */
/* 段用於保證 sizeof (heap_info) + 2 * SIZE_SZ 是按 MALLOC_ALIGNMENT 對齊的 */
char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;
內容來源
《glibc內存管理ptmallioc源代碼分析》
Glibc 官網 glibc2.31 源碼