tcache

Tcache는 glibc 2.26 (ubuntu 17.10) 이후에 도입 된 기술이며, 목적은 힙 관리 성능을 향상시키는 것입니다.

처음에 0x10~0x400이 할당되면 fastbin이나 unsorted bin에 들어가지 않고 tcache bin에 들어갑니다.

Tcache는 tcache_entry와 tcache_perthread_struct의 두 가지 새로운 구조를 도입했습니다.

이것은 실제로 패스트 빈과 매우 유사하지만 다릅니다.

tcache_entry

https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#tcache_entry

/* 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는 사용 가능한 청크 구조를 연결하는 데 사용되며, 다음 포인터는 동일한 크기의 다음 청크를 가르킵니다.

다음은 청크의 사용자 데이터를 가리키고 fastbin의 fd는 청크의 시작 부분에있는 주소를 가르킵니다.

또한 tcache_entry는 사용 가능한 청크의 사용자 데이터 부분을 다중화합니다.

tcache_perthread_struct

/* 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;

# define TCACHE_MAX_BINS                64

static __thread tcache_perthread_struct *tcache = NULL;

각 스레드는 전체 tcache의 관리 구조 인 tcache_prethread_struct를 유지 관리합니다. 총 TCACHE_MAX_BINS 카운터와 TCACHE_MAX_BINS 항목 tcache_entry가 있습니다.

tcache_entry는 fastbin과 마찬가지로 단일 링크 목록에서 동일한 크기의 (해제) 청크를 연결합니다. counts는 체인 당 최대 7 개의 청크와 함께 tcache_entry 체인의 사용 가능한 청크 수를 기록합니다.

다이어그램은 아마도 다음과 같습니다.

method

  • tcache가 채워질 때까지 tcache에 넣는다. (기본 값 7)
  • tcache가 다 채워지고 해제된 메모리는 fastbin이나 unsorted bin에 분류된다.
  • tcache의 chunk는 병합되지 않는다.

  • tcache가 비어있을 때 fastbin / smallbin / unsorted bin에 크기가 일치하는 청크가 있으면 fastbin / smallbin / unsorted bin의 청크가 가득 찰 때까지 tcache에 먼저 저장됩니다. 그런 다음 tcache에서 가져옵니다. 따라서 bin 및 tcache의 청크 순서가 반대로됩니다.

__libc_malloc

malloc이 호출되면 MAYBE_INIT_TCACHE ()으로 갑니다.

void *

__libc_malloc (size_t bytes)

{

    ......

    ......

#if USE_TCACHE

  /* int_free also calls request2size, be careful to not pad twice.  */

  size_t tbytes;

/ / Calculate the actual size of the chunk according to the parameters passed in malloc, and calculate the subscript corresponding to tcache
  checked_request2size (bytes, tbytes);

  size_t tc_idx = csize2tidx (tbytes);



/ / Initialize tcache
  MAYBE_INIT_TCACHE ();

  DIAG_PUSH_NEEDS_COMMENT;

If (tc_idx < mp_.tcache_bins // The idx obtained from size is within the legal range
      /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */

      && tcache

      && tcache->entries[tc_idx] != NULL) // tcache->entries[tc_idx] 有 chunk

    {

      return tcache_get (tc_idx);

    }

  DIAG_POP_NEEDS_COMMENT;

#endif
    ......

    ......

}

__tcache_init ()

tcache가 비어있을 때 MAYBE_INIT_TCACHE ()가 tcache_init ()를 호출 한다.

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); // find available arena
Victim = _int_malloc (ar_ptr, bytes); // Request a chunk of sizeof(tcache_prethread_struct) size  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) // initialize tcache
    {

      tcache = (tcache_perthread_struct *) victim;

      memset (tcache, 0, sizeof (tcache_perthread_struct));

    }

}

tcache_init () 성공적으로 리턴 한 후 tcache_prethread_struct가 생성된다.

tcache_put

/* 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 list에 chunk를 추가하는 함수이다. 해제된 청크를 보호없이 tcache entries[tc_idx]에 추가해준다.

_int_malloc, _int_free함수에서 호출된다.

_int_free

static void

_int_free (mstate av, mchunkptr p, int have_lock)

{

  ......

  ......

#if USE_TCACHE

  {

    size_t tc_idx = csize2tidx (size);

    if (tcache

        && tc_idx < mp_.tcache_bins // 64

        && tcache->counts[tc_idx] < mp_.tcache_count) // 7

      {

        tcache_put (p, tc_idx);

        return;

      }

  }

#endif
  ......

  ......

free함수를 호출 후 _int_free 함수를 호출하고 tc_idx가 유효한 것으로 판단 할 때 tcache count [tc_idx]가 7 내에 있으면 tcache_put ()를 호출하고 tcache bin이 비었으면 fastbin, unsorted bin에 들어가지 않고 tcache bin에 들어간다.

tcache에서는 전 버전들과 다르게 free할 때 dfb를 검사하지 않는다. 주소가 유효한지, 사이즈가 알맞은지 검사한다.

_int_malloc

size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
  {
    mchunkptr tc_victim;

    /* While bin not empty and tcache not full, copy chunks over.  */
    while (tcache->counts[tc_idx] < mp_.tcache_count
           && (pp = *fb) != NULL)
      {
        REMOVE_FB (fb, tc_victim, pp);
        if (tc_victim != 0)
          {
            tcache_put (tc_victim, tc_idx);
          }
      }
  }