fastbinsをgdbで見てみる
今日は短め。休日だし。
mallocで確保されてfreeされたヒープ領域は、arenaという仕組みによって管理されており、フラグメントの防止や再mallocの高速化などに貢献している。arenaはスレッドごとに一つ存在するため、マルチスレッドであれば複数のarenaができるわけだが今回はややこしいので一つのarenaに絞ってみていく。
デフォルトのarenaをmain_arenaと呼び、malloc_state構造体で定義されている。いつものようにglibcのソースコードを片手に内容を見ていく。
自分の環境は以下の通り。glibcのバージョンは2.31である。
まずmalloc_state構造体はglibc-2.31/malloc/malloc.cで定義されている。
struct malloc_state
{
/* Serialize access. */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast). */
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. */
int have_fastchunks;
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
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_state構造体の詳しい説明は下記の参考サイトを閲覧してほしい。今回重要になるのは mfastbinptr fastbinsY[NFASTBINS]の部分である。
fastbinsYでは小さめのメモリ領域を管理しており、前回のtcacheと同じくメモリの大きさごとにチャンクを管理している。
実際にプログラムを動かして、様子を見てみた。
#include <stdlib.h>
#include <stdio.h>
int main(void) {
char *ptr[15];
printf("\n");
for(int i=1; i<=10; i++) {
ptr[i] = (char *)malloc(1);
printf("ptr%d: %p\n", i, ptr[i]);
}
for(int i=1; i<=10; i++) {
free(ptr[i]);
}
return 0;
}
ptr1 ~ ptr10までmallocした後、ptr1 ~ ptr10の順にfreeしている。
実行するとこのようになる。0x20バイトずつ領域が割り当てられていることがわかる。この時のmain_arenaを見てみよう。
main_arenaのfastbinsY[0]にはアドレス0x5555555597c0が格納されている。これはptr10のチャンク-16バイトの位置を指している。青枠はtop変数を表しており、空きチャンクの先頭を表している。
fastbinsもtcacheと同じくLIFO方式であり、ptr1, ptr2, ... , ptr10の順番でfreeしたためptr10がチャンクリストの先頭に来ていることがわかる。
ここでチャンクはmalloc_chank構造体で定義されている。
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
fd_nextsize, bk_nextsizeは大きなサイズのチャンクでしか使われておらず、 またfastbinsではbkも使用されていないため、チャンクリストは単方向リストとなる。
ではこの時のptr1 ~ ptr10のメモリ領域について見てみる。(chunkは実際には重なる部分があるのだが、ややこしいので省略)
freeの順番を考えるとptr10 -> ptr9 -> ... -> ptr1のリストになっていそうだが、画像で色分けした通り実際にはptr10 -> ptr9 -> ptr8 と、 ptr7 -> ptr6 -> ... -> ptr1 という2つのチャンクリストに分けられている。これは前回説明したtcacheが関係しており、小さなサイズのチャンクはfastbinsよりもまずtcacheで管理されるようになっている。
/* This is another arbitrary limit, which tunables can change. Each
tcache bin will hold at most this number of chunks. */
# define TCACHE_FILL_COUNT 7
しかしtcacheのリストの最大サイズは7となっており、それを越えた量のチャンクがfastbinsで扱われている。図示するとこのようになっている。
最後にtcache_perthread_structがどうなっているかを見てみる。
赤枠部分がcounts[0], 青枠がentries[0]であり、それぞれチャンクの大きさ7, ptr7のアドレスを指していることがわかる。
【参考サイト】