mojo's Blog

malloc 구현 코드 리뷰 본문

학교에서 들은거 정리/시스템프로그래밍

malloc 구현 코드 리뷰

_mojo_ 2022. 5. 31. 11:59

간단한 "implicit free list" 를 기반으로 한 malloc 코드이다.

first-fit 과 next-fit 를 사용하며 boundary tag 를 이용하여 coalesce 를 한다.

그리고 블럭들은 doubleword(8 byte) 단위로 정렬이 되어야 하며 최소 블럭 사이즈는 16 byte 여야 한다. 

 

※ define 에 대한 설명

/*
 * If NEXT_FIT defined use next fit search, else use first fit search 
 */
#define NEXT_FITx

/* $begin mallocmacros */
/* Basic constants and macros */
#define WSIZE       4       /* Word and header/footer size (bytes) */ //line:vm:mm:beginconst
#define DSIZE       8       /* Doubleword size (bytes) */
#define CHUNKSIZE  (1<<12)  /* Extend heap by this amount (bytes) */  //line:vm:mm:endconst 

#define MAX(x, y) ((x) > (y)? (x) : (y))  

/* Pack a size and allocated bit into a word */
#define PACK(size, alloc)  ((size) | (alloc)) //line:vm:mm:pack

/* Read and write a word at address p */
#define GET(p)       (*(unsigned int *)(p))            //line:vm:mm:get
#define PUT(p, val)  (*(unsigned int *)(p) = (val))    //line:vm:mm:put

/* Read the size and allocated fields from address p */
#define GET_SIZE(p)  (GET(p) & ~0x7)                   //line:vm:mm:getsize
#define GET_ALLOC(p) (GET(p) & 0x1)                    //line:vm:mm:getalloc

/* Given block ptr bp, compute address of its header and footer */
#define HDRP(bp)       ((char *)(bp) - WSIZE)                      //line:vm:mm:hdrp
#define FTRP(bp)       ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE) //line:vm:mm:ftrp

/* Given block ptr bp, compute address of next and previous blocks */
#define NEXT_BLKP(bp)  ((char *)(bp) + GET_SIZE(((char *)(bp) - WSIZE))) //line:vm:mm:nextblkp
#define PREV_BLKP(bp)  ((char *)(bp) - GET_SIZE(((char *)(bp) - DSIZE))) //line:vm:mm:prevblkp
/* $end mallocmacros */

 

- NEXT_FITx : 이 경우는 first-fit 탐색을 해주며 next-fit 탐색을 해주고 싶으면 NEXT_FIT 으로

   변경시키면 된다.

   NEXT_FIT 가 선언되었는지 선언되지 않았는지를 판단하여 next-fit 을 돕는 tracking 용도의 변수가

   선언되는데 이는 전역변수에 대한 설명을 통해 알 수 있다.

 

- WSIZE : word size 는 4 byte 로 정의한 것을 알 수 있다.

 

- DSIZE : doubleword size 는 8 byte 로 WSIZE 의 두 배로 정의한 것을 알 수 있다.

 

- CHUNKSIZE : malloc 을 하다보면 free 공간을 찾는 과정에서 적당한 할당 공간을 찾을 수 없는 경우가 존재한다.   

   이 경우에는 memlib.c 에 static char * 타입으로 선언된 mem_brk 를 가지고 sbrk 함수를 통해서    

   brk pointer 를  늘려줘야 하는데 최소 CHUNKSIZE 만큼을 늘려주는 역할을 한다.   

   이 때, 할당할 공간의 사이즈 asize 가 CHUNKSIZE 보다 클 경우는 asize 만큼 brk pointier 를 늘려준다.

 

- PACK(size, alloc) : alloc 값은 0 또는 1 이며 이러한 값을 통해 해당 블럭이 free 한지, 할당되었는지를 알 수 있다.

   자세히 알아보면 doubleword(8 byte) 단위로 정렬해야 하며 block 사이즈는 최소 16 byte 임을 위에서 언급했다.

   이러한 성질을 이용한다면, size 값은 16 byte 이상이면서 8 byte 로 나머지 연산을 취할 경우 항상 0 이다.   

   즉, size = 16 + 8a (a ≥ 0) 이며 2 진수로 나타낼 경우 하위 3 개의 비트를 제외한 나머지 비트를 0 과 1 로    

   구성 되는 것을 알 수 있는데, 이를 통해 하위 3 개의 비트는 사이즈가 아닌 다른 용도로 쓰이는 것을 알 수 있다.   

   하위 3 개의 비트중 LSB 값에 따라 free 인지 할당되었는지를 판별할 수 있는데 다음과 같이 정리가 가능하다.

 

  • LSB = 0 : \(000_{2}\) 으로 블럭이 free 한 공간임을 알린다. 
  • LSB = 1 : \(001_{2}\) 으로 블럭이 할당된 공간임을 알린다.

   그러면 LSB 를 제외한 나머지 2 bit 는 어떠한 용도로 쓰일까?

   아쉽게도 나머지 2 bit 는 항상 0 bit 로 사용되지 않는 bit 이다.

 

- GET(p) : 포인터 타입 p 를 unsigned int * 타입으로 변환하여 p 가 가르키고 있는 공간에 존재하는 값을

   가져오는 것을 알 수 있다.

   implicit free list 에서는 boundary tag 를 사용하여 coalesce 를 하는데 구조를 대략적으로 알아야 할

   필요가 있어보인다.

 

   

   boundary tag 를 사용하면 앞, 뒤로 header, footer 에 대한 공간과 그 사이에 payload 및 padding 이

   붙어서 이러한 공간들을 하나의 block 이라고 하며 사이즈는 최소 16 byte 이상이며 8 byte 정렬을 지킨다.

   따라서, GET(p) 에서 p는 header 또는 footer 를 가르키고 있는 포인터가 될 수 있고, 이를 unsigned int *

   타입으로 변환하여 unsigned int 값인 size 를 읽어서 해당 블럭의 사이즈 및 할당 여부를 알 수 있게 된다.

 

- PUT(p, val) : p 의 타입을 unsigned int * 타입으로 변환하여 가르키고 있는 공간에 val 값을 넣는다.

   여기서 val 값은 해당 블럭의 사이즈 및 할당 여부를 알 수 있도록 하는 값이다.

 

- GET_SIZE(p) : GET(p) 를 통해 블럭의 size 및 할당 여부를 담고 있는 값을 가져오고 ~0x7 와 & 연산을 통해

   block 의 size 를 가져오게 된다.

   특정 블럭의 사이즈를 40 이라 하고 할당되었다고 가정하면 2진수로 \(101001_{2}\) 이다.

   여기서 0x7 = \(000111_{2}\) 을 not 을 하게 되면 ~0x7 = \(111000_{2}\) 이 된다.

   즉, 하위 3 비트를 제외한 나머지 비트를 추출해오는데 이는 결국 블럭의 사이즈가 되버린다.

   따라서, \(101001_{2}\) & \(111000_{2}\) = \(101000_{2}\) = 40 이 블럭 사이즈가 된다.

 

- GET_ALLOC(p) : GET_SIZE(p) 와 동일하게 GET(p) 를 통해 블럭의 size 및 할당 여부를 담고 있는 값을

   가져오고 0x1 과 & 연산을 통해 LSB 만을 가져오게 되는데 이는 0 또는 1 의 값으로 블럭의 할당 여부를 알 수 있다.

 

- HDRP(bp) : 블럭의 포인터 bp 를 통해 블럭의 header 가 가르키고 있는 주소를 받아오도록 한다.

   (char *)(bp) - WSIZE 를 통해 bp 의 주소는 header 에서 시작하는 것이 아님을 알 수 있으며 동시에

   WSIZE 만큼 빼서 header 의 주소를 받아오게 되므로 header 가 차지하고 있는 공간은 4 byte 임을 알 수 있다.

 

- FTRP(bp) : 블럭의 포인터 bp 를 통해 블럭의 footer 가 가르키고 있는 주소를 받아오도록 한다.

   (char *)(bp) +GET_SIZE(HDRP(bp)) - DSIZE 를 통해 footer 가 가르키고 있는 주소를 가져오게 되는데

   여기서 bp 가 가르키고 있는 주소에서 블럭 사이즈만큼 더해주면 다음 블럭의 주소를 가르키게 된다.

   따라서 WSIZE 가 아닌 DSIZE 를 빼줘야 footer 가 가르키고 있는 주소를 얻게 되며 WSIZE 를 빼게 되면

   자연스럽게 다음 블럭의 header 주소를 얻게 된다.

 

HDRP(bp), FTRP(bp) 를 가져오는 방식은 다음과 같이 가져오는 것을 알 수 있다.

 

 

- NEXT_BLKP(bp) : 현재 블럭에서 다음 블럭에 대한 주소값을 받아올 수 있다.

   (char *)(bp) + GET_SIZE((char *)(bp) - WSIZE) 에서 (char *)(bp) - WSIZE 가 가르키는 곳은 header 이다.

   따라서, GET_SIZE((char *)(bp) - WSIZE) 는 자연스럽게 현재 블럭에 대한 사이즈가 되며 이를 더하게 되면

   다음 블럭에 대한 주소를 얻게 된다.

 

- PREV_BLKP(bp) : 현재 블럭에서 이전 블럭에 대한 주소값을 받아올 수 있다.

   (char *)(bp) - GET_SIZE((char *)(bp) - DSIZE) 에서 (char *)(bp) - DSIZE 가 가르키는 곳은 footer 이다.

   따라서, 위와 같은 연산을 통해 이전 블럭에 대한 주소를 얻게 된다.

 

 

※ 전역 변수에 대한 설명

/* Global variables */
static char *heap_listp = 0;  /* Pointer to first block */  
#ifdef NEXT_FIT
static char *rover;           /* Next fit rover */
#endif

 

- heap_listp : 첫번째 block 의 pointer 를 담당하는 용도이다.

 

- rover : define 을 통해 NEXT_FIT 이 정의된 경우 rover 를 통해 next-fit 탐색을 돕도록 한다.

   정의되지 않은 경우 해당 변수는 사용되지 않는다.

 

 

※ int mm_init(void) 

 

int mm_init(void) 
{
    /* Create the initial empty heap */
    if ((heap_listp = mem_sbrk(4*WSIZE)) == (void *)-1) //line:vm:mm:begininit
	return -1;
    PUT(heap_listp, 0);                          /* Alignment padding */
    PUT(heap_listp + (1*WSIZE), PACK(DSIZE, 1)); /* Prologue header */ 
    PUT(heap_listp + (2*WSIZE), PACK(DSIZE, 1)); /* Prologue footer */ 
    PUT(heap_listp + (3*WSIZE), PACK(0, 1));     /* Epilogue header */
    heap_listp += (2*WSIZE);                     //line:vm:mm:endinit  
/* $end mminit */

#ifdef NEXT_FIT
    rover = heap_listp;
#endif
/* $begin mminit */

    /* Extend the empty heap with a free block of CHUNKSIZE bytes */
    if (extend_heap(CHUNKSIZE/WSIZE) == NULL) 
	return -1;
    return 0;
}

 

malloc 을 하기 위해선 초기화 작업은 필수다.

따라서 초기에 malloc 을 할 경우 해당 함수가 호출이 된다.

처음에 mem_sbrk(4*WSIZE) 를 호출함으로써 heap_listp 의 시작 주소를 변경시켜 준다.

그렇다면 mem_sbrk 함수는 어떤 매커니즘으로 수행이 되는지 확인이 필요하다.

 

static char *mem_heap;     /* Points to first byte of heap */ 
static char *mem_brk;      /* Points to last byte of heap plus 1 */
static char *mem_max_addr; /* Max legal heap addr plus 1*/ 

void mem_init(void)
{
    mem_heap = (char *)Malloc(MAX_HEAP);
    mem_brk = (char *)mem_heap;               
    mem_max_addr = (char *)(mem_heap + MAX_HEAP); 
}

void *mem_sbrk(int incr) 
{
    char *old_brk = mem_brk;

    if ( (incr < 0) || ((mem_brk + incr) > mem_max_addr)) {
	errno = ENOMEM;
	fprintf(stderr, "ERROR: mem_sbrk failed. Ran out of memory...\n");
	return (void *)-1;
    }
    mem_brk += incr;
    return (void *)old_brk;
}

 

mem_sbrk 함수를 살펴보기 전에 전역 변수로 선언된 mem_heap, mem_brk, mem_max_addr 가

어떠한 용도로 사용되는지를 알아야 할 필요가 있어보인다.

우선 mem_init() 함수를 통해 전역 변수들에 대한 초기화 작업이 진행된다.

 

 

위와 같이 MAX_HEAP 사이즈 만큼 동적 할당을 통해 공간이 생성되며 mem_brk 는 시작부분,

mem_max_addr 는 mem_heap 의 가장 마지막 부분을 가르키게 된다.

이 주소들은 위에서 언급한 heap_listp 및 rover 가 해당 영역을 가르키게 된다.

그러면, 4*WSIZE 값을 파라미터로 받아와서 mem_sbrk 함수를 수행하는데 old_brk 는 mem_brk 의

주소를 할당받게 되며 이 주소값을 반환하여 heap_listp 에 주소를 할당해주는 것을 알 수 있다.

조건문을 보면 incr 값이 0 미만이거나 mem_max_addr 주소를 넘어가는 경우 에러 처리를 해준다.

에러가 없을 경우, mem_brk += incr 을 해주고 주소값 (void *)old_brk 를 반환하게 된다.

따라서 이러한 과정을 통해 다음과 같이 mem_brk 의 주소값이 변경되는 것을 알 수 있다.

 

 

다시 mm_init 함수로 돌아가면, 현재 heap_listp 는 mem_heap 주소를 가르키고 있으며 4 번의 PUT 이

연달아 일어나는 것을 알 수 있다.

이를 순차적으로 수행하면 다음과 같다.

 

  1. 시작 부분인 heap_listp 에 0 을 넣는다. (가장 앞 부분인 4 byte 는 사용하지 않음을 암시)
  2. heap_listp + (WSIZE) 에 DSIZE | 1 을 넣는다. (header 에 해당)
  3. heap_listp + (2*WSIZE) 에 DSIZE | 1 을 넣는다. (footer 에 해당)
  4.  heap_listp + (3*WSIZE) 에 0 | 1 을 넣는다. 

그리고 heap_listp 의 주소를 2 * WSIZE 만큼 이동시킨다.

만약 NEXT_FIT 이 선언된 경우에 rover 는 heap_listp 와 동일한 주소값을 갖도록 된다.

이러한 과정을 걸치면 다음과 같이 표현할 수 있다.

 

 

그 후에 extend_heap(CHUNKSIZE/WSIZE) 를 호출하여 heap 의 사이즈를 늘려주는걸 알 수 있다.

여기서 CHUNKSIZE/WSIZE 는 \(2^{12} / 4 = 2^{10}\) 으로 인자값을 \(2^{10}\) 으로 넘겨준다. 

extend_heap 함수는 다음과 같다.

 

static void *extend_heap(size_t words) 
{
    char *bp;
    size_t size;

    /* Allocate an even number of words to maintain alignment */
    size = (words % 2) ? (words+1) * WSIZE : words * WSIZE; //line:vm:mm:beginextend
    if ((long)(bp = mem_sbrk(size)) == -1)  
	return NULL;                                        //line:vm:mm:endextend

    /* Initialize free block header/footer and the epilogue header */
    PUT(HDRP(bp), PACK(size, 0));         /* Free block header */   //line:vm:mm:freeblockhdr
    PUT(FTRP(bp), PACK(size, 0));         /* Free block footer */   //line:vm:mm:freeblockftr
    PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); /* New epilogue header */ //line:vm:mm:newepihdr

    /* Coalesce if the previous block was free */
    return coalesce(bp);                                          //line:vm:mm:returnblock
}

 

우선 파라메터로 받은 words 는 \(2^{10}\) 이다.

size 는 words 값이 홀수일 경우 (words + 1) * WSIZE 를 하여 할당을 해주는 것으로 보아 doubleword 단위로

정렬하는 것을 알 수 있다.

그러면 현재 words 값은 짝수이므로 자연스럽게 size 는 \(2^{12}\) 이며 mem_sbrk(size) 를 호출하여

받아온 주소값을 bp 에 할당해주는 것을 알 수 있다.

그 후에 3 번의 PUT 작업을 수행하면 다음과 같이 나타낼 수 있다.

 

 

그 후에 coalesce(bp) 를 호출한 후에 받아온 주소값을 반환하게 된다.

그러면 coalesce 함수를 보도록 한다.

 

static void *coalesce(void *bp) 
{
    size_t prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp)));
    size_t next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp)));
    size_t size = GET_SIZE(HDRP(bp));

    if (prev_alloc && next_alloc) {            /* Case 1 */
	return bp;
    }

    else if (prev_alloc && !next_alloc) {      /* Case 2 */ 
	size += GET_SIZE(HDRP(NEXT_BLKP(bp)));
	PUT(HDRP(bp), PACK(size, 0));
	PUT(FTRP(bp), PACK(size, 0));
    }

    else if (!prev_alloc && next_alloc) {      /* Case 3 */
	size += GET_SIZE(HDRP(PREV_BLKP(bp)));
	PUT(FTRP(bp), PACK(size, 0));
	PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
	bp = PREV_BLKP(bp);
    }

    else {                                     /* Case 4 */
	size += GET_SIZE(HDRP(PREV_BLKP(bp))) + 
	    GET_SIZE(FTRP(NEXT_BLKP(bp)));
	PUT(HDRP(PREV_BLKP(bp)), PACK(size, 0));
	PUT(FTRP(NEXT_BLKP(bp)), PACK(size, 0));
	bp = PREV_BLKP(bp);
    }
/* $end mmfree */
#ifdef NEXT_FIT
    /* Make sure the rover isn't pointing into the free block */
    /* that we just coalesced */
    if ((rover > (char *)bp) && (rover < NEXT_BLKP(bp))) 
	rover = bp;
#endif
/* $begin mmfree */
    return bp;
}

 

 

우선, prev_alloc, next_alloc 값은 현재 블럭 bp 를 기준으로 이전 블럭과 다음 블럭의 할당 여부를 알 수 있는

0, 1 인 값이 들어가게 된다.

 

 

초록색으로 된 블럭을 할당된 블럭, 하늘색으로 된 블럭을 free 블럭으로 보자.

그러면 prev_alloc = 1, next_alloc = 1 이므로 양 쪽 전부 할당된 상태이며, size 값은 현재 bp 블럭의 사이즈이다.

따라서, case 1 에서 조건이 만족되므로 coalesce 없이 bp 를 반환하게 된다.

자연스럽게 extend_heap 함수에서 호출한 coalesce(bp) 값을 반환하게 되고 mm_init 함수에서

extend_heap(CHUNKSIZE/WSIZE) 값이 NULL 이 아니므로 최종적으로 0을 반환하며

mm_init 함수가 종료된다.

 

그러면 coalesce 함수의 나머지 케이스인 case 2, 3, 4 를 살펴보도록 하자.

이해를 쉽게 초록색을 할당된 블럭, 하늘색을 free 블럭, 보라색을 중간에 free 될 블럭이라 생각하고 살펴본다.

 

- case 2 : 이전 블럭이 할당되있고 다음 블럭이 free 블럭인 경우

   free 블럭인 다음 블럭과 coalesce 를 해야 하므로 size 에 다음 블럭의 사이즈를 더해준다.

   현재 블럭의 header 와 다음 블럭의 footer 에 PACK(size, 0) 값을 할당해준다.

   

 

- case 3 : 이전 블럭이 free 블럭이며 다음 블럭이 할당된 블럭인 경우

   free 블럭인 이전 블럭과 coalesce 를 해야 하므로 size 에 이전 블럭의 사이즈를 더해준다.

   이전 블럭의 header 와 현재 블럭의 footer 에 PACK(size, 0) 값을 할당해준다.

   그리고 bp 는 이전 free 블럭으로 이동해줘야 한다. 

 

 

- case 4 : 양쪽 블럭 전부 free 블럭인 경우

   양쪽 블럭이 free 블럭으로 양쪽 블럭과 coalesce 를 해야 하므로 size 에 이전 블럭과 다음 블럭의 사이즈를

   더해준다.

   이전 블럭의 header 와 다음 블럭의 footer 에 PACK(size, 0) 값을 할당해준다.

   그리고 case 3 과 동일하게 bp 는 이전 free 블럭으로 이동해줘야 한다.

 

 

 

※ mm_malloc(size_t size)

 

void *mm_malloc(size_t size) 
{
    size_t asize;      /* Adjusted block size */
    size_t extendsize; /* Amount to extend heap if no fit */
    char *bp;      

/* $end mmmalloc */
    if (heap_listp == 0){
	mm_init();
    }
/* $begin mmmalloc */
    /* Ignore spurious requests */
    if (size == 0)
	return NULL;

    /* Adjust block size to include overhead and alignment reqs. */
    if (size <= DSIZE)                                          //line:vm:mm:sizeadjust1
	asize = 2*DSIZE;                                        //line:vm:mm:sizeadjust2
    else
	asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE); //line:vm:mm:sizeadjust3

    /* Search the free list for a fit */
    if ((bp = find_fit(asize)) != NULL) {  //line:vm:mm:findfitcall
	place(bp, asize);                  //line:vm:mm:findfitplace
	return bp;
    }

    /* No fit found. Get more memory and place the block */
    extendsize = MAX(asize,CHUNKSIZE);                 //line:vm:mm:growheap1
    if ((bp = extend_heap(extendsize/WSIZE)) == NULL)  
	return NULL;                                  //line:vm:mm:growheap2
    place(bp, asize);                                 //line:vm:mm:growheap3
    return bp;
}

 

heap_listp 값이 0 이면 최초로 malloc 함수를 호출한 것을 알 수 있다.

따라서, 이 경우에 mm_init() 을 하여 아래와 같은 구조를 만들어야 malloc 진행이 가능하다.

 

 

size 값에 따라서 다음과 같이 3 가지 케이스로 분류가 가능하다.

 

  1. size = 0 : NULL 을 리턴한다. (할당할 블럭이 없으니깐)
  2. size <= DSIZE : asize = 2*DSIZE 를 해준다. (Minimum block size = 16 (2*DSIZE))
  3. size > DSIZE : asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE) 를 해준다. (asize = 16, 24, 32, ... 으로 doubleword 단위로 정렬)

 

그 후에 find_fit(asize) 를 호출하여 할당 공간을 찾고 해당 주소를 bp 에 넣어준다.

그러면 find_fit 함수에 대해서 살펴보도록 한다.

 

static void *find_fit(size_t asize)
/* $end mmfirstfit-proto */
{
/* $end mmfirstfit */

#ifdef NEXT_FIT 
    /* Next fit search */
    char *oldrover = rover;

    /* Search from the rover to the end of list */
    for ( ; GET_SIZE(HDRP(rover)) > 0; rover = NEXT_BLKP(rover))
	if (!GET_ALLOC(HDRP(rover)) && (asize <= GET_SIZE(HDRP(rover))))
	    return rover;

    /* search from start of list to old rover */
    for (rover = heap_listp; rover < oldrover; rover = NEXT_BLKP(rover))
	if (!GET_ALLOC(HDRP(rover)) && (asize <= GET_SIZE(HDRP(rover))))
	    return rover;

    return NULL;  /* no fit found */
#else 
/* $begin mmfirstfit */
    /* First fit search */
    void *bp;

    for (bp = heap_listp; GET_SIZE(HDRP(bp)) > 0; bp = NEXT_BLKP(bp)) {
	if (!GET_ALLOC(HDRP(bp)) && (asize <= GET_SIZE(HDRP(bp)))) {
	    return bp;
	}
    }
    return NULL; /* No fit */
/* $end mmfirstfit */
#endif
}

 

find_fit 함수는 할당할 수 있는 곳을 찾으면 바로 해당 주소를 반환하고 그 외엔 NULL 을 반환하다.

여기서 바로 해당 주소를 반환하는 것으로 best-fit 은 고려하지 않는다. (오직 first-fit, next-fit)

first-fit, next-fit 두 가지 케이스가 존재하는데, next-fit 부터 살펴보도록 한다.

 

- next-fit : oldrover 에 rover 를 할당한 후에 next-fit 탐색을 한다.

   우선, rover 를 시작으로 하여 GET_SIZE(HDRP(rover)) = 0 일 때까지(마지막 도달) 탐색하면서 두 조건을

   만족할 경우 해당 rover 를 반환해준다.  

 

  1. free 공간이다. (GET_ALLOC(HDRP(rover)) = 0)
  2. 공간이 충분한 알맞는 공간이다. (asize <= GET_SIZE(HDRP(rover))

   그리고 끝까지 탐색하지 못찾을 경우, 처음부터 oldrover(최초 rover) 까지 탐색을 시도한다.

   모든 탐색을 시도했음에도 발견하지 못할 경우 NULL 을 반환하게 된다.

 

- first-fit : 첫 시작인 heap_listp 부터 전부 탐색을 시도하여 적합한 곳을 발견하면 반환하고

   모든 탐색을 시도했음에도 발견하지 못할 경우 NULL 을 반환해준다.

 

다시 mm_malloc 함수로 돌아와서 적합한 곳을 찾아 bp 에 할당해주었다고 하자.

그러면 place(bp, asize) 를 통해 해당 bp 에 asize 만큼의 블럭을 만들어준다.

place 함수에 대해서 살펴보도록 한다.

 

static void place(void *bp, size_t asize)
     /* $end mmplace-proto */
{
    size_t csize = GET_SIZE(HDRP(bp));   

    if ((csize - asize) >= (2*DSIZE)) { 
	PUT(HDRP(bp), PACK(asize, 1));
	PUT(FTRP(bp), PACK(asize, 1));
	bp = NEXT_BLKP(bp);
	PUT(HDRP(bp), PACK(csize-asize, 0));
	PUT(FTRP(bp), PACK(csize-asize, 0));
    }
    else { 
	PUT(HDRP(bp), PACK(csize, 1));
	PUT(FTRP(bp), PACK(csize, 1));
    }
}

 

우선, csize 는 현재 free 블럭의 사이즈를 할당한 것을 알 수 있다.

편의를 위해 초록색을 asize 만큼 할당하고자 할 블럭, 하늘색을 free 블럭으로 두고 두 가지 케이스로

나눠서 살펴보도록 하자.

 

- csize - asize >= 2*DSIZE :

   bp 의 header, footer 에 asize 를, 그리고 남은 사이즈 csize - asize 를 bp 의 다음 블럭의 header, footer 에

   넣어준다.

 

 

- csize - asize < 2*DSIZE :

   이 케이스는 약간 특별하다.

   우선, 블럭의 최소 사이즈는 16 byte 라는 것을 감안할 때, asize 를 16, csize 를 24 라고 하면 나머지 8 byte

   를 free 공간으로 남기게 된다면 이는 블럭의 최소 사이즈보다 더 작아지게 된다.

   따라서, 이러한 문제를 방지하기 위해, csize 만큼의 블럭을 할당해버리는데 나머지 8 byte 는 결국 padding

   을 첨가하여 해결이 가능하게 된다.

 

 

다시 mm_malloc 함수로 돌아오자.

만약 find_fit 함수를 호출하여 적합한 곳을 찾지 못하는 경우도 분명 존재할 것이다.

이 경우에는 brk pointer 를 증가시켜야 한다.

따라서 extendsize = MAX(asize, CHUNKSIZE) 를 통해 asize, CHUNKSIZE 둘 중에 더 큰 값을

extendsize 에 넣어줘야 한다.

그리고 extend_heap 함수를 호출시켜 정상적으로 brk pointer 를 증가시킨 경우, place(bp, asize)

를 실행한다.

그럼에도 불구하고 brk pointer 를 증가시키지 못하여 NULL 이 반환된 경우, mm_malloc 함수에서

파라메터로 받아온 size 또는 CHUNKSIZE 만큼 brk pointer 를 증가시키지 못한다.

 

 

※ void mm_free(void *bp)

 

void mm_free(void *bp)
{
/* $end mmfree */
    if(bp == 0) 
	return;

/* $begin mmfree */
    size_t size = GET_SIZE(HDRP(bp));
/* $end mmfree */
    if (heap_listp == 0){
	mm_init();
    }
/* $begin mmfree */

    PUT(HDRP(bp), PACK(size, 0));
    PUT(FTRP(bp), PACK(size, 0));
    coalesce(bp);
}

 

파라메터로 받아온 void * 타입의 bp 에 대해서 해당 주소가 0 인 경우는 return 처리해준다.

size 값은 현재 bp 에 해당하는 블럭의 사이즈를 받아온다.

만약 heap_listp 가 0 인 경우는 한번도 malloc 을 하지 않은 상태이므로 mm_init() 을 해준다.

그리고 현재 free 하고자 하는 블럭의 header, footer 에 PACK(size, 0) 값을 넣어줌으로써

free 상태로 마킹한 후, coalesce 를 시도한다.

 

 

※ void *mm_realloc(void *ptr, size_t size)

 

void *mm_realloc(void *ptr, size_t size)
{
    size_t oldsize;
    void *newptr;

    /* If size == 0 then this is just free, and we return NULL. */
    if(size == 0) {
	mm_free(ptr);
	return 0;
    }

    /* If oldptr is NULL, then this is just malloc. */
    if(ptr == NULL) {
	return mm_malloc(size);
    }

    newptr = mm_malloc(size);

    /* If realloc() fails the original block is left untouched  */
    if(!newptr) {
	return 0;
    }

    /* Copy the old data. */
    oldsize = GET_SIZE(HDRP(ptr));
    if(size < oldsize) oldsize = size;
    memcpy(newptr, ptr, oldsize);

    /* Free the old block. */
    mm_free(ptr);

    return newptr;
}

 

이제 마지막 함수에 대한 설명이다.

이 함수는 기존의 ptr의 블럭 사이즈를 파라메터로 받아온 size 만큼 블럭 크기를 변경해주도록 한다.

우선, 파라메터로 받아온 size 값이 0 일 경우, 단순하게 받아온 블럭을 free 해주면 그만이다.

그리고 블럭의 해당 주소가 NULL 일 경우, 이 경우는 block 이 존재하지 않은 상태이다.

따라서 malloc 을 통해 새로 만들어주면 된다. 

 

newptr = mm_malloc(size) 를 실행한 부분은 size 값이 적어도 1 이상이면서 ptr 이 NULL 이 아니다.

즉, 새롭게 할당한 newptr 을 통해 기존에 있던 ptr 을 newptr 에 복사한 후, ptr 을 free 시켜야 한다.

이 때, ptr 의 블럭 사이즈가 파라메터로 받아온 size 보다 클 경우 memcpy 를 하는 과정에서 불필요한

메모리 공간을 침범하는 경우가 발생할 수 있으므로 oldsize 에 size 를 할당하여 memcpy 를 해야 한다.

memcpy 를 완료하면 기존에 있던 ptr 을 free 해주고 새로 할당한 newptr 을 반환해주면 된다.

 

'학교에서 들은거 정리 > 시스템프로그래밍' 카테고리의 다른 글

Garbage collection  (0) 2022.05.27
Malloc  (0) 2022.05.27
Dymaic Memory Allocation  (0) 2022.05.22
Parallelism  (0) 2022.05.17
Synchronization : Advanced  (0) 2022.05.12
Comments