Profile

youngsouk

youngsouk

_int_malloc 함수 분석 (1)

INTERNAL_SIZE_T nb;               /* 요청한 크기를 알맞게 가공한 변수*/ 
  unsigned int idx;                 /* 연관된 bin의 index */
  mbinptr bin;                      /* 연관된 bin */

  mchunkptr victim;                 /* 검사할 or 선택된 청크 */
  INTERNAL_SIZE_T size;             /* 그것의 크기 */
  int victim_index;                 /* 그것의 bin index */

  mchunkptr remainder;              /* 분할하고 남은 나머지 */
  unsigned long remainder_size;     /* 그것의 크기*/

  unsigned int block;               /* bit map traverser */
  unsigned int bit;                 /* bit map traverser */
  unsigned int map;                 /* current word of binmap */
  /*bit map관련해서는 따로 올리겠습니다.ㅠ.ㅠ

  mchunkptr fwd;                    /* link 과정을 위한 임시 */
  mchunkptr bck;                    /* link 과정을 위한 임시 */

이 함수에 쓰일 여러가지 변수를 선언해준다.

#if USE_TCACHE
  size_t tcache_unsorted_count;	    /* count of unsorted chunks processed */
#endif

tcache를 사용한다면 tcache의 unsorted chunk 갯수를 저장하는 변수를 선언한다.

 

 /*
     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 traps (returning 0) request sizes
     that are so large that they wrap around zero when padded and
     aligned.
   */

  checked_request2size (bytes, nb);

이 checked_request2size 함수의 원형은 이렇다,

#define checked_request2size(req, sz) \
({				    \
  (sz) = request2size (req);	    \
  if (((sz) < (req))		    \
      || REQUEST_OUT_OF_RANGE (sz)) \
    {				    \
      __set_errno (ENOMEM);	    \
      return 0;			    \
    }				    \
})

이 함수를 보면 알 수 있듯이, 알맞게 크기가 가공되었는지 검사하는 함수이다.

  /* There are no usable arenas.  Fall back to sysmalloc to get a chunk from
     mmap.  */
  if (__glibc_unlikely (av == NULL))
    {
      void *p = sysmalloc (nb, av);
      if (p != NULL)
	alloc_perturb (p, bytes);
      return p;
    }

unlikely로 좀 더 효율적으로 if문을 처리하고 있다. av가 NULL즉 arena가 제대로 얻어지지 않았을 때에 참이 된다.

이 때에는 sysmalloc으로 메모리를 할당해준다.

정상적으로 할당이 되면, alloc_perturb를 호출하는데 이 기능은 잘 모르겠다. 나중에 perturb byte에 관해 올리겠습니다.

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

만약 크기가 fastbin에 들어갈 크기라면 먼저 상응하는 bin을 확인한다. 이 코드는 av 즉 아레나가 시작이 아직 안되었어도 안전해서, 시간을 단축하기 위해 확인없이 실행한다는 얘기다.

#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);					\
// REMOVE_FB 끝

다음으로 REMOVE_FB라는 함수를 정의하는데 이 함수의 이름으로 추측하건데 fastbin을 초기화하는것 같다.

 

victim에 pp를 저장한다.

그런데 victim이 NULL 즉 pp에 아무값이 없었다면 나가게 된다.

do ~while의 조건문 안에 있는 조건식은 잘모르겠다. 나중에 올리겠습니다. ㅠ.ㅠ

  if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
    {
      idx = fastbin_index (nb);
      mfastbinptr *fb = &fastbin (av, idx);
      mchunkptr pp;
      victim = *fb;

nb가 fastbin에 들어갈 수 있는 최대크기 이하인지 확인한다.

들어갈 수 있으면, 크기를 통해 fastbin의 index를 구한다. 

fb에 index를 이용해 구한 fastbin의 주소를 저장한다,

원리는 fastbin 함수의 원형이 이렇게 선언되어있기 때문이다.

#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])

이렇게 idx를 이용해 fastbin에 있는 값을 뽑아오는데, &연산자를 사용했으므로 주소를 리턴해주게 된다.

여기서 주의 할 것이 fb는 fastbin에 대한 이중포인터가 아니라는 것이다.

왜냐하면 기본적으로 mfastbinptr이

typedef struct malloc_chunk *mfastbinptr;

이렇게 정의되지만

#include <stdlib.h>
#include <stdio.h>

struct malloc_chunk {
	int prev_size;
	int size;
//	  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;
};

int main(){
	typedef struct malloc_chunk *mfastbinptr;
	typedef struct malloc_chunk* mchunkptr;
	mfastbinptr fastbinsY[50] = {malloc(sizeof(struct malloc_chunk))};

	mfastbinptr *fb = &fastbinsY[0];

	printf("fb : %p\n", fb);
	printf("&fastbinY[0] : %p\n", &fastbinsY[0]);
	printf("*fb : %p\n", *fb);
	printf("fastbinsY[0] : %p\n", fastbinsY[0]);
}

이것을 컴파일 후 실행시켜보면 

fb : 0x7fff287c9650

&fastbinY[0] : 0x7fff287c9650

*fb : 0x1d49010

fastbinsY[0] : 0x1d49010

이렇게 *fb랑 fastbinsY[0]이랑 같게 나오기 때문이다.

 

다음으로 pp라는 청크 포인터도 선언해준다.

victim에는 fb가 가리키는 값 즉, 위에서 설명한 fastbin[idx]를 가져오게 된다.

      if (victim != NULL)
	{
	  if (SINGLE_THREAD_P)
	    *fb = victim->fd;
	  else
	    REMOVE_FB (fb, pp, victim);

값을 잘 가져왔을 때 실행된다. 

단일 스레드이면 fb가 가리키는 곳에 victim의 fd를 넣는다. 즉 위에서 설명했듯이 *fb는 fastbin[idx]이다. 따라서 fastbin[idx]에 idx에 맞는 fastbin에 있던 청크의 fd를 넣어준다. 즉 unlink과정이랑 같다고 볼 수 있다. 

단일 스레드가 아니면 REMOVE_FB를 실행하는데 이 함수는 위에서 설명했듯이 fastbin을 초기화시키는 역할을 한다.

 

	  if (__glibc_likely (victim != NULL))
	    {
	      size_t victim_idx = fastbin_index (chunksize (victim));
	      if (__builtin_expect (victim_idx != idx, 0))
		malloc_printerr ("malloc(): memory corruption (fast)");
	      check_remalloced_chunk (av, victim, nb);

vicim이 NULL이 아닐 때 진입하게된다.

victim_idx에 크기에 맞는 index를 저장한다,

그런데 만약 victim_idx가 idx와 맞지 않는다면 즉, 메모리에 이상이 생기면 error를 일으킨다. 

그런뒤에 check_remalloced_chunk로 검사를 하게된다. 자세한건 다른 포스팅에다가 하겠다.

#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);
	      if (tcache && tc_idx < mp_.tcache_bins)

d다음은 tcache를 사용할때 진입하게된다,

tc_idx에 chunk size를 tcache의 index로 바꾸어주는 함수를 이용해서 알맞는 index를 저장한다,

만약 tcache에 값이 있고, tcache의 index가 올바르면 들어가게된다.

		  mchunkptr tc_victim;

		  /* bin이 비어있지 않고 tcahe가 풀로 채워지지 않으면 청크를 복사한다.  */
		  while (tcache->counts[tc_idx] < mp_.tcache_count
			 && (tc_victim = *fb) != NULL)
		    {
		      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);
		    }
		}
#endif

tc_victim을 선언해준다.

tcache의 index에 따른 갯수가 한계가 아니고, tc_victim이 NULL이 아니면 무한 반복한다. 동시에 tc_victim을 *fb 즉 fastbin[idx]가 저장된다.

 

단일 스레드일 때 *fb 위에서 설며했듯이 fastbin[idx]에 tc_victim -> fd, 즉 위에서 설명했던 과정이랑 같다. 

단일 스레드가 아니면 fastbin을 초기화하고, tc_victim이 NULL이면 반복문을 빠져나간다.

다음에는 tcache_put 함수로 tcache에 tc_victim을 넣어준다.

이 과정을 while문으로 돌리게 되면, tcache가 가득차거나 bin이 빌때까지 tcache에 chunk를 넣게 됩니다.

 

tcache관련 함수 분석은 여기에 있습니다.

2019/08/08 - [힙(heap)] - tcache 분석

 

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_..

youngsouk-hack.tistory.com

__unlikely에 대해 궁금하시다면

2019/08/09 - [힙(heap)] - __builtin_expect, likely, unlikely

 

__builtin_expect, likely, unlikely

malloc관련 문서나 커널 문서에 가면 많이 보이는 이 함수들은 기능이 결과적으로 보면 비슷하다. 다 if문을 좀 더 효율적으로 동작시키기 위한 장치이다. 기본적으로 __builtin_expect는 __builtin_expect(조건식..

youngsouk-hack.tistory.com

여길 참고해주세요.

 

	      void *p = chunk2mem (victim);
	      alloc_perturb (p, bytes);
	      return p;
	    }
	}
    }

이제 얻어 왔던 victim을 chunk2mem함수를 이용해 사용자에게 주는 포인터를 만든다..

perturb관련은 나중에 따로 하겠습니다.

그런뒤에 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.)
   */

  if (in_smallbin_range (nb))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);

bin안의 검색이 필요하지 않아서 빠르게 할 수 있다는 소리이다.

크기가 smallbin 범위하면 들어가게 된다.

idx에 크기에 따른 smallbin의 index를 구하여 넣게 된다.

bin_at함수를 통해 idx에 따른 bin의 주소를 구한다.

      if ((victim = last (bin)) != bin)
        {
          bck = victim->bk;
	  if (__glibc_unlikely (bck->fd != victim))
	    malloc_printerr ("malloc(): smallbin double linked list corrupted");
          set_inuse_bit_at_offset (victim, nb);
          bin->bk = bck;
          bck->fd = bin;

victim에는 last(bin) 을 저장해준다.  last함수는 이렇게 정의된다.

#define last(b)      ((b)->bk)

이 if문은 bin에 어떤 값들이 저장되어있을 때 즉 free된 것이 있을 때 들어가게 된다. 그 이유는 차후에 bin구조 관련해서 포스팅 할때 설명하겠습니다.

bck에는 victim -> bk를 불러온다.

bck -> fd 가 victim인지 검사한다. 즉 이 bk와 fd를 불러오는 것은 bin의 double linked list구조가 망가지지 않았나 보는 것이다.

set_inuse_bit_at_offset함수를 통해서 다음 청크의 prev_inuse비트를 활성화시킨다.

그런 뒤 bin -> bk에 bck, 즉 victim -> bk를 넣는다. 

또 bck - > fd에 bin주소를 저장한다.

여기서 중요하게 볼 점은 smallbin이 FIFO 방식이라는 것이다. 왜냐하면 저 2과정이 제일 먼저 free된 것의 연결을 끊어주는 과정이기 때문이다.

          if (av != &main_arena)
	    set_non_main_arena (victim);
          check_malloced_chunk (av, victim, nb);

만약 main_arena가 아니라면 non_main_arena비트를 셋팅해준다. 그런 뒤 check를 해준다. 이것에 관해서는 차후 포스팅에서 언급할 것이다.

 

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

_int_malloc함수 분석(3)  (0) 2019.08.12
_int_malloc 함수 분석 (2)  (0) 2019.08.09
__libc_free함수 분석  (0) 2019.08.09
__libc_malloc함수 분석  (0) 2019.08.09
__builtin_expect, likely, unlikely  (0) 2019.08.09