Profile

youngsouk

youngsouk

sysmalloc() 분석(코드 분석&&순서도)

/* ----------- 시스템 할당을 다루기 위한 함수이다. -------------- */

/*
   sysmalloc은 시스템으로부터 더 많은 메모리를 요구하는 경우(즉 top chunk의 크기가 부족할 때) 실행된다. 
   시작부분에, av->top(top chunk)이 요청한 크기를 할당하기에 충분한 크기가 없다고 추정된다.
   그러므로 av->top(top chunk)가 확장되거나 대체될 필요가 있다.
 */

static void *
sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{
  mchunkptr old_top;              /* incoming value of av->top */
  INTERNAL_SIZE_T old_size;       /* 그것의 크기 */
  char *old_end;                  /* 그것의 끝 주소 */

  long size;                      /* 메모리 확장을 위한 첫번째 인자 */
  char *brk;                      /* MORECORE의 반환값 */

  long correction;                /* 2번째 MORECORE의 인자 */
  char *snd_brk;                  /* 2nd return val */

  INTERNAL_SIZE_T front_misalign; /* unusable bytes at front of new space */
  INTERNAL_SIZE_T end_misalign;   /* partial page left at end of new space */
  char *aligned_brk;              /* aligned offset into brk */

  mchunkptr p;                    /* the allocated/returned chunk */
  mchunkptr remainder;            /* remainder from allocation */
  unsigned long remainder_size;   /* its size */


  size_t pagesize = GLRO (dl_pagesize);
  bool tried_mmap = false;


  /*
  	 만약 mmap을 가지고 있고, 요청한 크기가 mmap threshold(mmap의 최소)이상이고, 
     시스템이 mmap을 지원하고, 몇가지 mmap으로 할당된 충분한 지역이 있다면, 바로 top chunk
     를 확장시키기보다는 이 요청을 바로 mapping시키려 시도한다.
   */

  if (av == NULL 
      || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
      // 요구한 크기가 mmap 최소보다 크고, mmap으로 할당된 갯수가 최대 아래라면 참이 된다.
	  && (mp_.n_mmaps < mp_.n_mmaps_max)))
    {
      char *mm;           /* mmap을 통해 반환되는 값 */

    try_mmap:
      /*
      	 크기를 가장 근접한 페이지로 올림한다. mmap으로 할당된 청크들은, 평범한 청크들 보다 크고,
         SIZE_SZ 단위이다.왜냐하면 prev_size 비트가 사용가능한 청크들은 올 수 없기 때문이다.
         아래의 front_misalign을 다루는 것을 보아라, glibc에서 우리가 높은 정렬을 하지 않는 한,
         더이상 정렬을 할 필요가 없다.
       */
      if (MALLOC_ALIGNMENT == 2 * SIZE_SZ) // 올림을 하는 부분이다.
      // 다만 올림을 할때 page단위로 올림을 하게되는데 page크기는 보통 0x1000이다. 
      // 즉 크기가 0x1000의 배수가 되게 올림해준다.
        size = ALIGN_UP (nb + SIZE_SZ, pagesize);
      else
        size = ALIGN_UP (nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
      tried_mmap = true;


      /* 만약 크기가 0에 근접한다면 시도하지 마십시오 */
      if ((unsigned long) (size) > (unsigned long) (nb)) // 크기는 우리가 올림을 해주었으니 무조건 요구한 크기보다 크게된다.
        {
        // mmap을 통해 할당해주게 된다.
          mm = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));

          if (mm != MAP_FAILED) // mmap 할당에 실패하지 않는다면
            {
              /*
              	 mmap으로 할당된 지역의 처음의 offset은 그 청크의 prev_size 필드에 저장된다. 이것은 우리가
                 반환되는 시작 주소를 여기서 요구되는 정렬과 memalign()안의 정렬 요구를 만족하는것을 가능하게 하고,
                 나중에 free()안의 munmap 그리고 realloc()의 인자의 적절한 주소를 계산하는 것이 
                 가능하게 한다.
              */

              if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
                {
                  /* 
                  	 glibc에서 chunk2mem 함수는 주소를 2 * SIZE_SZ만큼 증가 시키고, MALLOC_ALIGN_MASK
                     은 2*SIZE_SZ-1이다. 각각 mmap으로 할당한 지역은 page정렬되었고, 그러므로
                     명백히 MALLOC_ALIGN_MASK으로 정렬되어있다.
                  */
                  // MALLOC_ALIGN_MASK으로 정렬 되어있다는 것은 64bit 기준 MALLOC_ALIGN_MASK은
                  // 15 즉 2진수로 나타내면 1111이 된다. 즉 주소값이 16의 배수인지 확인하는 것이다.
                  assert (((INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK) == 0);
                  front_misalign = 0;
                }
              else
              //사용자가 MALLOC_ALIGNMENT를 정의했을 떄이다.
              //front_misalign을 MALLOC_ALIGN_MASK로 정렬(주소가 64bit일경우에는 16의 배수, 32bit일경우에는 8의 배수여야한다.)
              //되어있을때와의 값의 차이를 저장한다. 여기서 이 차이가 나는 바이트는 무시, 즉 사용되지 않는다.
              //왜냐하면 리턴되는 값을 MALLOC_ALIGN_MASK로 정렬되게 하기 위해서이다.
                front_misalign = (INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK;
              if (front_misalign > 0)
                {
                //그 차이를 이용해 정렬을 해준다.
                  correction = MALLOC_ALIGNMENT - front_misalign;
                  p = (mchunkptr) (mm + correction);
		  set_prev_size (p, correction);
          //mmap을 통해 할당되어었다는 것을 알려준다.
                  set_head (p, (size - correction) | IS_MMAPPED);
                }
              else
                {
                  p = (mchunkptr) mm;
		  set_prev_size (p, 0);
                  set_head (p, size | IS_MMAPPED);
                }

              /* 상태를 업데이트한다. */

              int new = atomic_exchange_and_add (&mp_.n_mmaps, 1) + 1;
              atomic_max (&mp_.max_n_mmaps, new);

              unsigned long sum;
              sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size;
              atomic_max (&mp_.max_mmapped_mem, sum);

              check_chunk (av, p);

              return chunk2mem (p);
            }
        }
    }

  /* 사용가능한 아레나가 없고, mmap이 또한 실패했을 때이다. */
  if (av == NULL) // 여기까지 진행이 되었다는 것은 아레나가 무조건 있어야한다. 
  				  // 왜냐하면 위에서 av가 NULL이면 위의조건문이 참이되어서 mmap으로 할당해주게 되기 때문이다.
    return 0;

  /* top chunk의 수정을 기록한다. */

  old_top = av->top; // 예전 top chunk를 저장한다.
  old_size = chunksize (old_top);  // 예전 top chunk의 크기를 저장한다.
  old_end = (char *) (chunk_at_offset (old_top, old_size)); // 예전 top chunk의 마지막 주소를 저장한다.

  brk = snd_brk = (char *) (MORECORE_FAILURE); // MORECORE_FAILURE = -1

  /*
  	 만약 처음이 아니라면, 우리는 예전 크기가 최소 크기 이상이고, 
     prev_inuse 비트가 항상 셋팅되어있어야한다.(top chunk 였기 때문이다.)
   */

	// 예전의 top chunk주소가 맞는지, 정렬이 잘 되었는지, prev_size를 확인한다.
  assert ((old_top == initial_top (av) && old_size == 0) ||
          ((unsigned long) (old_size) >= MINSIZE &&
           prev_inuse (old_top) &&
           ((unsigned long) old_end & (pagesize - 1)) == 0));

  /* 필수 조건: 요구한 크기를 충족할 만한 크기가 없어야한다. */
  assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));


  if (av != &main_arena)
    {
      heap_info *old_heap, *heap;
      size_t old_heap_size;

      /* 처음에는 현재 힙을 확장시키려 한다. */
      old_heap = heap_for_ptr (old_top);
      old_heap_size = old_heap->size;
      if ((long) (MINSIZE + nb - old_size) > 0
          && grow_heap (old_heap, MINSIZE + nb - old_size) == 0) // 힙을 확장할 수 있는지 확인한다. arena.c에 정의되어 있다.
        {
        //시스템으로 메모리가 할당되어있으므로 malloc_state 구조체인 av를 갱신해준다.
          av->system_mem += old_heap->size - old_heap_size;
          set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
                    | PREV_INUSE);
        }
      else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
        {
          /* 새로 할당한 힙을 사용한다.  */
          heap->ar_ptr = av;
          heap->prev = old_heap;
          av->system_mem += heap->size;
          /* 새로운 top chunk를 할당한다.  */
          top (av) = chunk_at_offset (heap, sizeof (*heap));
          set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);

          /* top chunk의 울타리를 설정하고, 예전의 top chunk를 MALLOC_ALIGNMENT의 배수
         	 크기로 free시켜준다. */
          /* 그 울타리는 최소 크기를 너어야 한다. 왜냐하면 그것은 나중에 top chunk가 다시 될
          	 수도 있기 때문이다. 그 청크가 사용중일지라도, 끝부분이 셋팅이 된다. */
          old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
          set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
          if (old_size >= MINSIZE)
            {
              set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
              set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
              set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
              _int_free (av, old_top, 1);
            }
          else
            {
              set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
              set_foot (old_top, (old_size + 2 * SIZE_SZ));
            }
        }
      else if (!tried_mmap) // 최소 1번 sysmalloc()에서 mmap으로 할당을 안했다면 참이된다.
        /* 우리는 최소 mmap 메모리를 사용할 수 있다. */
        goto try_mmap;
    }
  else     /* av == main_arena */


    { /* 요구한 크기 + pad + 헤더 정보까지의 충분한 크기를 요구한다. */
      size = nb + mp_.top_pad + MINSIZE;

      /*
      	 만약 연속적이라면, 우리는 우리가 새로운 공간과 결합하기를 원하는 예전 공간을 뺼 수 있다.
         우리는 연속적인 공간을 실제로 얻지 못할지라도 나중에 더해준다.
       */

      if (contiguous (av))
        size -= old_size;

      /*
      	 page 크기의 배수만큼 올려준다.
         만약 MORECORE가 연속적이지 않다면, 이것은 우리가 page로 정렬된 인자로만 
         그것을 호출한다는 것을 확실하게 해준다.
         그리고 만약 MORECORE가 연속적이고, 이것이 처음이 아니라면, 이것은 이전 호출의 page로 정렬된 
         인자를 보존한다. 그렇지 않으면, 우리는 page정렬을 바로 잡는다.
       */

      size = ALIGN_UP (size, pagesize);

      /*
      	 만약 인자가 음수로 나타낼만큼 큰 수(underflow)를 인자로 호출하지 마십시오. mmap이 
         size_t 인자를 가지기 때문에, 그것은 우리가 MORECORE을 호출하지 못할지라도, 
         뒤에 이어지게 된다.
       */

      if (size > 0)
        {
          brk = (char *) (MORECORE (size)); // MORECORE = sbrk
          LIBC_PROBE (memory_sbrk_more, 2, brk, size);
        }

      if (brk != (char *) (MORECORE_FAILURE))
        {
          /* 만약 필요하다면 morecore hook을 호출한다. */
          void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
          if (__builtin_expect (hook != NULL, 0))
            (*hook)();
        }
      else
        {
          /*
          	 만약 mmap을 가지고 있다면, 그것을 MORECORE가 실패하거나 사용될 수 없을때의 백업으로
             사용하는 것을 시도한다. 이것은 주소 공간에 구멍을 가진 시스템에서는 가치가 없는 것이여서
             sbrk가 연속된 공간으로 확장하지 못하지만, 공간이 어디에서든지 사용가능하다.
             우리는 mmap 최대 갯수 그리고 한계를 무시하는 것을 주의해라. 왜냐하면 그 공간은 
             한정된 mmap 지역으로서 사용될 수 없기 때문이다.
           */

          /* 예전의 top chunk와 합칠 수 업어서, 그것의 크기를 뒤에서 더해준다. */
          if (contiguous (av))
            size = ALIGN_UP (size + old_size, pagesize);

          /* 만약 우리가 mmap을 backup으로서 의존한다면 더 큰 단위를 사용한다. */
          if ((unsigned long) (size) < (unsigned long) (MMAP_AS_MORECORE_SIZE)) //MMAP_AS_MORECORE_SIZE = 1024 * 1024
            size = MMAP_AS_MORECORE_SIZE;

          /* 만약 크기가 0 근처라면 시도하지 마십시오. */
          if ((unsigned long) (size) > (unsigned long) (nb))
            {
              char *mbrk = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));

              if (mbrk != MAP_FAILED)
                {
                  /* 우리는 끝을 찾기 위해 다른 sbrk를 호출할 필요도 없고 사용할 수도 없다. */
                  brk = mbrk;
                  snd_brk = brk + size;

                  /*
                  	 우리는 더 이상 연속적인 sbrk 지역을 가지지 않는다는 것을 기록하자.
                     처음 mmap이 backup으로 사용된 후에, 이것이 부정확하게 지역이 이어질 수
                     있기 때문에 우리는 연속적인 공간에 집중 할 필요가 없다.
                   */
                  set_noncontiguous (av);
                }
            }
        }

      if (brk != (char *) (MORECORE_FAILURE))
        {
          if (mp_.sbrk_base == 0)
            mp_.sbrk_base = brk;
          av->system_mem += size;

          /*
             만약 MORECORE가 전의 공간에서 확장된다면 우리는 똑같이 top chunk의 크기를 확장시킬 수 있다.
           */

          if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE))
            set_head (old_top, (size + old_size) | PREV_INUSE);

          else if (contiguous (av) && old_size && brk < old_end)
	    /* 이런!  누군가가 우리의 공간을 없앴내요... 아무것도 건드리지 마세요  */
	    malloc_printerr ("break adjusted to free malloc space");

          /*
          	 그렇지 않으면, 수정을 합니다:
           * 만약 처음이거나 메모리가 연속적이지 않다면 우리는 어디에 메모리의 끝이 있는지
           	 찾기 위해 sbrk를 호출해야한다. 
           * 우리는 malloc에서 반환된 모든 청크들은 MALLOC_ALIGNMENT로 정렬되어 있다는 것이
           	 확실히 할 필요가 있다.
           * 만약 동시에 외부 sbrk가 있다면, 우리는 sbrk 요청 크기를 우리가 old_top안에 존재하는 공간과
           	 새로운 공간을 결합할 수 없다는 사실을 충족하도록 조정할 필요가 있다.
           * 거의 모든 시스템들이 우리가 요청한 마지막 페이지 전체를 쓰는 경우에도
             내부적으로 한번에 전체 page를 할당한다. 그래서 우리는 page 경계에 부딪히게 
             충분한 크기를 할당하고, 결과적으로 나중에 contiguous 호출이 page-align(page 정렬)을 일으킨다.
           */

          else
            {
              front_misalign = 0;
              end_misalign = 0;
              correction = 0;
              aligned_brk = brk;

              /* 연속적인 경우를 취급한다. */
              if (contiguous (av))
                {
                  /* 외부 sbrk를 system_mem로서 갯수를 센다. */
                  if (old_size)
                    av->system_mem += brk - old_end;

                  /* 이 공간으로부터 만들어진 첫번째 새로운 청크의 정렬을 보장한다. */

                  front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
                  if (front_misalign > 0)
                    {
                      /*
                      	 정렬된 위치에 도착한 몇몇 바이트들은 무시한다.(왜냐하면 MALLOC_ALIGN_MASK정렬
                         -64bit는 16의 배수 32bit는 8의 배수) 유지하기 위해서이다.)
                         우리는 특별히 이러한 낭비되는 앞 바이트들을 표시할 필요가 없다.
                         그들은 절대로 접근 될 수 없다. 왜냐하면 top chunk의 prev_inuse
                         (그리고 그것의 시작에서만들어지는 아무 청크)가 할당된 이후에는 
                         항상 참이 되기 때문이다.
                       */

                      correction = MALLOC_ALIGNMENT - front_misalign;
                      aligned_brk += correction;
                    }

                  /*
                  	 만약 이것이 존재하는 공간에 가깝지 않다면, 그럼녀 우리는 예전 top chunk의 공간과
                     결합할 수 없다. 그래서 2번째 요청까지 추가해야한다.
                   */

                  correction += old_size;

                  /* 끝 주소를 page 경계에 다다르게 확장한다. */
                  end_misalign = (INTERNAL_SIZE_T) (brk + size + correction);
                  correction += (ALIGN_UP (end_misalign, pagesize)) - end_misalign;

                  assert (correction >= 0);
                  snd_brk = (char *) (MORECORE (correction));

                  /*
                  	 만약 정확히 할당할 수 없다면, 최소 현재 brk에서 찾아본다.
                     그것은 실패없이 실행되기에 충분하다. 만약 2번째 sbrk가 실패하지 않는다면,
                     우리는 공간이 연속적이라고 추측할 수 있다. 이것은 프로그램이 다중 스레드
                     프로그램이 아닌한 안전하다. 하지만 lock과 첫번째 그리고 2번째 호출 사이에
                     일어난 외부 sbrk를 사용하지 않는다.
                   */

                  if (snd_brk == (char *) (MORECORE_FAILURE))
                    {
                      correction = 0;
                      snd_brk = (char *) (MORECORE (0));
                    }
                  else
                    {
                      /* 만약 필요하다면 morecore_hook을 실행시킨다.  */
                      void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
                      if (__builtin_expect (hook != NULL, 0))
                        (*hook)();
                    }
                }

              /* 연속적이지 않을때를 취급한다. */
              else
                {
                  if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
                    /* MORECORE/mmap은 정확히 정렬되어있어야 한다. */
                    assert (((unsigned long) chunk2mem (brk) & MALLOC_ALIGN_MASK) == 0);
                  else
                    {
                      front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
                      if (front_misalign > 0)
                        {
                          /*
                     	 정렬된 위치에 도착한 몇몇 바이트들은 무시한다.(왜냐하면 MALLOC_ALIGN_MASK정렬
                         -64bit는 16의 배수 32bit는 8의 배수) 유지하기 위해서이다.)
                         우리는 특별히 이러한 낭비되는 앞 바이트들을 표시할 필요가 없다.
                         그들은 절대로 접근 될 수 없다. 왜냐하면 top chunk의 prev_inuse
                         (그리고 그것의 시작에서만들어지는 아무 청크)가 할당된 이후에는 
                         항상 참이 되기 때문이다.
                           */

                          aligned_brk += MALLOC_ALIGNMENT - front_misalign;
                        }
                    }

                  /* 현재 메모리의 끝 부분을 찾는다. */
                  if (snd_brk == (char *) (MORECORE_FAILURE))
                    {
                      snd_brk = (char *) (MORECORE (0));
                    }
                }

              /* 2번째 sbrk의 결과를 바탕으로 top chunk를 수정한다. */
              if (snd_brk != (char *) (MORECORE_FAILURE)) 
                {
                  av->top = (mchunkptr) aligned_brk;
                  set_head (av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE);
                  av->system_mem += correction;

                  /*
                  	 만약 처음이 아니라면, 우리는 외부 sbrk 또는 비연속적인 지역 때문에 틈이 있다.
                     old_top 부분에 우리가 소유하지 않은 공간의 병합을 막기 위해 2개의 울타리를
                     삽입한다. 이러한 울타리는 inuse_bit가 셋팅되어있고, 사용하기에는 너무작은 인공적인
                     청크(2 * SIZE_SZ크기)이다. 우리는 크기와 정렬을 원할하게 하기 윈해서 2개가 필요하다.
                   */

                  if (old_size != 0)
                    {
                      /*
                      	 old_top을 울타리를 삽입하기 위해 MALLOC_ALIGNMENT의 배수 만큼의
                         크기를 유지하면서 축소시킨다. 우리는 old_top에 이것을 하기 위한 최소한의
                         공간이 있다는 것을 안다.
                       */
                      old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
                      set_head (old_top, old_size | PREV_INUSE);

                      /*
                      	 뒤에 나오는 할당은 예전 top chunk의 크기가 최소 크기에 가까울 때
                         완전히 예전의 top chunk를 덮어쓴다. 이것은 의도적인다. 우리는 
                         예전의 top chunk가 손실이 있을지라도 울타리가 필요하다.
                       */
		      set_head (chunk_at_offset (old_top, old_size),
				(2 * SIZE_SZ) | PREV_INUSE);
		      set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ),
				(2 * SIZE_SZ) | PREV_INUSE);

                      /* 만약 가능하다면 나머지를 free시킨다. */
                      if (old_size >= MINSIZE)
                        {
                          _int_free (av, old_top, 1);
                        }
                    }
                }
            }
        }
    } /* if (av !=  &main_arena) */

  if ((unsigned long) av->system_mem > (unsigned long) (av->max_system_mem))
    av->max_system_mem = av->system_mem;
  check_malloc_state (av);

  /* 마지막으로 할당을 한다. */
  p = av->top;
  size = chunksize (p);

  /* 위에 있는 할당 경로들 중 하나가 성공했는지 체크한다.(즉 우리가 요구한 요청이 잘 처리되었는지 확인) */
  if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
    {
      remainder_size = size - nb;
      remainder = chunk_at_offset (p, nb);
      av->top = remainder;
      set_head (p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
      set_head (remainder, remainder_size | PREV_INUSE);
      check_malloced_chunk (av, p, nb);
      return chunk2mem (p);
    }

  /* 모든 실패 결로를 처리한다. */
  __set_errno (ENOMEM);
  return 0;
}

https://drive.google.com/file/d/13uAd1uFnTvrXnE2zKX7MNZHez0gbHryc/view?usp=sharing

 

sysmalloc.drawio

 

drive.google.com

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

_int_free 분석  (0) 2019.08.20
_int_malloc함수 분석(3)  (0) 2019.08.12
_int_malloc 함수 분석 (2)  (0) 2019.08.09
_int_malloc 함수 분석 (1)  (0) 2019.08.09
__libc_free함수 분석  (0) 2019.08.09