Profile

youngsouk

youngsouk

tcache 분석

/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

tcache_entry 구조체의 모습이다. next라는 이름의 포인터밖에 없다.

/* There is one of these for each thread, which contains the
   per-thread cache (hence "tcache_perthread_struct").  Keeping
   overall size low is mildly important.  Note that COUNTS and ENTRIES
   are redundant (we could have just counted the linked list each
   time), this is for performance reasons.  */
typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

tcache_perthread_struct 구조체의 모습이다. counts에는 free된 chunk의 갯수를 저장하는 배열이다. entries 포인터 배열은 가장 최근에 free된 청크의 주소를 기억한다.

static __thread bool tcache_shutting_down = false;
static __thread tcache_perthread_struct *tcache = NULL;

위에꺼는 tcache가 종료?되었는지를 나타내는 변수이다. true면 tcache가 종료되었다는 말이다. 아래의 것은 다양한 함수(tcache에 넣는거, 제거하는거 등)에 쓰일 변수 초기화이다.

/* Caller must ensure that we know tc_idx is valid and there's room
   for more chunks.  */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx < TCACHE_MAX_BINS);
  e->next = tcache->entries[tc_idx];
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);

tcache안에 넣을 때이다. chunk2mem 함수는 인자 + 2 * size_t 이다. 즉 chunk의 fd부분을 반환한다. 

tc_idx즉 넣을 청크의 index가 최대 크기를 넘는지 검사한다.

e->next가 의미하는 바는 tcache_entry 구조체의 모습을 위에서 보았다면 알 수 있는데 e에는 대상 청크의 fd부분의 주소가 담겨져 있다, 따라서 e -> next가 의미하는 바는 대상 청크의 fd부분이다. 거기에 원래 있던 tc_idx에 따른 entries의 값을 넣는다.

그런 뒤 tc_idx에 따른 entries배열에 대상청크를 넣어줌으로서 linked list 형태가 만들어지게 된다.

마지막에 tc_idx에 따른 count를 증가시켜준다.

 

/* Caller must ensure that we know tc_idx is valid and there's
   available chunks to remove.  */
static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  assert (tc_idx < TCACHE_MAX_BINS);
  assert (tcache->entries[tc_idx] > 0);
  tcache->entries[tc_idx] = e->next;
  --(tcache->counts[tc_idx]);
  return (void *) e;
}

tcache_get부분인데 이 부분은 청크가 tcache에서 제거될 때 사용된다.

 

tc_idx에 따른 entries배열의 값을 뽑아와서 e에 저장된다. 여기서 tcache는 LIFO방식이라는 것을 알 수 있다. 왜냐하면 entries배열에는 가장 최근에 free된 청크의 주소를 저장하고 있기 때문이다.

tc_idx가 최대갯수를 넘으면 프로그램을 종료시킨다.

올바른 주소값을 얻지 못하면 프로그램을 종료시킨다.

entries 배열에 e -> next 즉 2번째로 최근에 free된 함수를 넣어준다.

그런뒤 갯수를 1감소시킨다.

static void
tcache_thread_shutdown (void)
{
  int i;
  tcache_perthread_struct *tcache_tmp = tcache;

  if (!tcache)
    return;

  /* Disable the tcache and prevent it from being reinitialized.  */
  tcache = NULL;
  tcache_shutting_down = true;

  /* Free all of the entries and the tcache itself back to the arena
     heap for coalescing.  */
  for (i = 0; i < TCACHE_MAX_BINS; ++i)
    {
      while (tcache_tmp->entries[i])
	{
	  tcache_entry *e = tcache_tmp->entries[i];
	  tcache_tmp->entries[i] = e->next;
	  __libc_free (e);
	}
    }

  __libc_free (tcache_tmp);
}

다음으로는 tcache를 완전히 종료시킬때이다.

종료시키기 전에 tcahe_tmp에다가 tcache를 임시저장한다.

tcache의 값이 NULL이라면 tcache가 이미 종료되어있다는 것이므로 함수를 종료한다.

tcache에 NULL값을 넣어준다.

tcache가 꺼졌다는 표시로 tcache_shutting_down에 참을 셋팅해준다.

다음에는 모든 tcache의 chunk를 free하는 과정이다. 

마지막으로는 tcache_tmp를 free하면서 함수가 종료된다.

static void
tcache_init(void)
{
  mstate ar_ptr;
  void *victim = 0;
  const size_t bytes = sizeof (tcache_perthread_struct);

  if (tcache_shutting_down)
    return;

  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.  */
  if (victim)
    {
      tcache = (tcache_perthread_struct *) victim;
      memset (tcache, 0, sizeof (tcache_perthread_struct));
    }

}

tcache_init함수인데 복잡해보여도 기능은 간단하다. tcache가 종료되어있다면 이 함수를 종료하고, arena를 얻어오는 것을 시도. 안되면 다시 시도한다. 

성공적으로 얻어왔으면, 얻어온 값들을 memset으로 초기화 시켜준다.

 

'힙(heap) > glibc' 카테고리의 다른 글

__libc_free함수 분석  (0) 2019.08.09
__libc_malloc함수 분석  (0) 2019.08.09
__builtin_expect, likely, unlikely  (0) 2019.08.09
__libc_calloc 함수 분석  (0) 2019.08.09
힙 보안검사(지속적으로 추가 예정)  (0) 2019.08.05