fastbinsをgdbで見てみる

今日は短め。休日だし。


mallocで確保されてfreeされたヒープ領域は、arenaという仕組みによって管理されており、フラグメントの防止や再mallocの高速化などに貢献している。arenaはスレッドごとに一つ存在するため、マルチスレッドであれば複数のarenaができるわけだが今回はややこしいので一つのarenaに絞ってみていく。

デフォルトのarenaをmain_arenaと呼び、malloc_state構造体で定義されている。いつものようにglibcのソースコードを片手に内容を見ていく。

自分の環境は以下の通り。glibcのバージョンは2.31である。

画像1


まず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している。

画像2

実行するとこのようになる。0x20バイトずつ領域が割り当てられていることがわかる。この時のmain_arenaを見てみよう。

画像3

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は実際には重なる部分があるのだが、ややこしいので省略)

画像4

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で扱われている。図示するとこのようになっている。

画像5

最後にtcache_perthread_structがどうなっているかを見てみる。

画像6

赤枠部分がcounts[0], 青枠がentries[0]であり、それぞれチャンクの大きさ7, ptr7のアドレスを指していることがわかる。


【参考サイト】


いいなと思ったら応援しよう!