
- 아래의 모든 내용은 CS:APP 책의 9장 내용을 바탕으로, NEXT-FIT 방법론을 이용해 작성하였다.
//*********************************************************
/* Basic constants and macros */
/* double word (8) alignment */
#define ALIGNMENT 8
/* rounds up to the nearest multiple of ALIGNMENT */
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)
#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))
#define WSIZE 4 // word size = 4bytes
#define DSIZE 8 // double word size = 8bytes
#define CHUNKSIZE (1<<12) //Extend heap by this amount
#define MAX(x,y) ((x)>(y)?(x):(y))
/* Pack a size and allocated bit into a word (header)*/
#define PACK(size,alloc) ((size)|(alloc))
/* Read and write a word at address p */
#define GET(p) (*(unsigned int *)(p))
#define PUT(p, val) (*(unsigned int *)(p) = (val))
/* Raed the size and allocated fields from address p */
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)
/* Given block ptr bp, compute address of its header and footer */
#define HDRP(bp) ((char*)(bp) - WSIZE)
#define FTRP(bp) ((char*)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)
/* Given block ptr bp, compute address of next and previous blocks */
#define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE(((char*)(bp)-WSIZE)))
#define PREV_BLKP(bp) ((char *)(bp) - GET_SIZE(((char*)(bp)-DSIZE)))
//*********************************************************
/* double word (8) alignment */
#define ALIGNMENT 8
우선, ALIGNMENT는 정렬 조건을 설정해준다. 위와 같이 설정하면 정렬 조건은 8bytes ( = double word)가 된다. 우리는 앞으로 8의 배수 크기로 메모리를 할당해줄 수 있는 것이다.
말인 즉, 우리는 heap 공간의 한 칸을 word(=4bytes) 단위로 볼 것이므로, 앞으로 메모리 할당 요청이 들어오면 '짝수 칸 씩 할당 해 주겠다' 라는 말로 쉽게 치환 해 볼 수 있다.
/* rounds up to the nearest multiple of ALIGNMENT */
#define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)
10 + (8-1) = 17 일 것이다. 이는 7을 더해 올림을 해주기 위한 준비 과정으로 생각하면 되겠다.17 & ~0x7 연산을 이진수로 생각해보자.
#define CHUNKSIZE (1<<12) //Extend heap by this amount
1<<12는 결과적으로 2¹² = 4096과 같다. 이는 4096(bytes)를 의미하고, CHUNKSIZE라는 것은 힙을 새로 확장할 때 기준 단위다.
따라서, CHUNKSIZE를 1<<12로 설정하겠다는 말은 '한 번 힙을 확장할 때 마다 4096bytes를 기준으로 확장하겠다.'는 말이다.
4096 bytes는 범용적으로 활용하는 기본적인 단위다.
/* Pack a size and allocated bit into a word (header)*/
#define PACK(size,alloc) ((size)|(alloc))
/* Raed the size and allocated fields from address p */
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)
우선 PACK(size,alloc)에 대해 먼저 살펴보자. 이는 메모리 블록의 header와 footer를 설정할 때, size (크기) 정보와 alloc (할당 여부) 정보를 합쳐주는 연산이다.
24bytes크기의 할당 된 (1) 블록을 예로 들어보자.

위와 같이 이진수로 확인해보면 시각적으로 size 정보와 alloc 정보가 합쳐진다는 것이 무슨 말인지 보일 것이다.
참고로, 우리는 정렬 조건을 double word (=8bytes)로 설정 했으므로, size 정보는 하위 3비트를 제외한 부분이 된다. (무조건 8 이상의 배수이므로)
위의 개념을 이해했다면, GET_SIZE(p)와 GET_ALLOC(p)는 직관적으로 이해할 수 있다. GET_SIZE(p)를 먼저 살펴보자.

GET_SIZE(p)는 ~0x7과 AND 연산을 통해 하위 3비트를 0으로 바꾸어 size 정보만 추출한다.

GET_ALLOC(p)는 0x1과 AND 연산을 통해 하위 1비트만을 남겨 alloc 정보를 추출하는 것이다.
/* Read and write a word at address p */
#define GET(p) (*(unsigned int *)(p))
#define PUT(p, val) (*(unsigned int *)(p) = (val))
/* Given block ptr bp, compute address of its header and footer */
#define HDRP(bp) ((char*)(bp) - WSIZE)
#define FTRP(bp) ((char*)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)
/* Given block ptr bp, compute address of next and previous blocks */
#define NEXT_BLKP(bp) ((char *)(bp) + GET_SIZE(((char*)(bp)-WSIZE)))
#define PREV_BLKP(bp) ((char *)(bp) - GET_SIZE(((char*)(bp)-DSIZE)))
GET(p)와 PUT(p,val)은 해당 포인터 주소의 값을 가져오거나 수정하는 역할이다.HDRP(bp), FTRP(bp)는 각각 해당 블록 포인터의 header와 footer의 주소를 반환해주는 역할이다.NEXT_BLKP(bp), PREV_BLKP(bp)는 각각 다음 블록 포인터의 주소, 이전 블록 포인터의 주소를 반환한다.int mm_init(void)
{
// void * heap_listp = NULL;
/* Create the initial empty heap */
if ((heap_listp = mem_sbrk(4*WSIZE)) == (void *)-1)
return -1;
/* prologue block */
PUT(heap_listp, 0);
PUT(heap_listp + (1*WSIZE), PACK(DSIZE,1));
PUT(heap_listp + (2*WSIZE), PACK(DSIZE,1));
/* eplilogue block */
PUT(heap_listp + (3*WSIZE), PACK(0,1));
heap_listp += (2*WSIZE);
last_bp = heap_listp;
if (extend_heap(CHUNKSIZE/WSIZE) == NULL)
return -1;
return 0;
}

last_bp는 이후 find_fit() 함수를 NEXT-FIT 방법으로 구현할 때 쓸 것이다.참고) mem_sbrk (memlib.h)
mem_sbrk는 증가시킬 메모리 공간의 크기를 매개변수로 받아 그 만큼 mem_brk를 확장해주는 함수이다.
단, incr이 음수이거나 힙의 크기를 넘어가면 에러 (-1)를 반환한다.
/*
* mem_sbrk - simple model of the sbrk function. Extends the heap
* by incr bytes and returns the start address of the new area. In
* this model, the heap cannot be shrunk.
*/
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;
}
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;
if ((long)(bp = mem_sbrk(size)) == -1)
return NULL;
/* Initialize free block header/footer */
PUT(HDRP(bp), PACK(size,0));
PUT(FTRP(bp), PACK(size,0));
/* Set epilogue block */
PUT(HDRP(NEXT_BLKP(bp)), PACK(0,1));
return coalesce(bp);
}
static void *extend_heap(size_t words)
char * bp;
char* bp;에서 자료형 char*을 사용해준 이유는 char이 1byte 이기 때문에 그렇다. 바이트 단위로 연산을 수행할 것이기 때문이지, '문자를 사용하나?' 하고 혼동해서는 안된다.size = (words % 2) ? (words+1)*WSIZE: words*WSIZE;
if ((long)(bp = mem_sbrk(size)) == -1)
return NULL;
이후 mem_sbrk를 호출해 size 만큼 확장한다.
여기까지 실행했을 때, 현재 힙의 상태는 아래와 같을 것이다.

아직 header도, footer도, epilogue block도 제대로 설정되지 않았다. 아래 코드를 이어가보자.
/* Initialize free block header/footer */
PUT(HDRP(bp), PACK(size,0));
PUT(FTRP(bp), PACK(size,0));
/* Set epilogue block */
PUT(HDRP(NEXT_BLKP(bp)), PACK(0,1));
header와 footer, epilogue block이 알맞게 설정되면서, 비로소 아래와 같은 상태가 된다.

이후, coalesce( ) 함수를 호출 해 연속된 비할당 블록들을 연결 해준다.
( mm_init에서는 이제 막 초기화를 하는 단계이기 때문에 의미가 없지만, 동적 할당을 이용하다 필요한 순간에 비어있는 메모리들이 연결 될 것이다.)
return coalesce(bp);
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(HDRP(NEXT_BLKP(bp)));
PUT(HDRP(PREV_BLKP(bp)), PACK(size,0));
PUT(FTRP(PREV_BLKP(bp)), PACK(size,0));
bp = PREV_BLKP(bp);
}
last_bp = bp;
return bp;
}

/* Place block of asize bytes at start of free block bp
* and split if remainder would be at least minimum block size */
static void place(void *bp, size_t asize)
{
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));
}
}
asize = 메모리 할당에서 요청된 블록 크기
csize = 현재 블록의 크기
if ((csize-asize) >= ((2*DSIZE)) :: "남은 공간이 충분하다면" ~ 블록을 쪼갠다.

남은 공간이 충분하지 않다면 csize 만큼만 할당한다.

위 그림을 보고 잘 이해가 가지 않는다면, 아래 예시 그림 참고

위에서 블록의 최소 크기가 2*DSIZE인 이유?
- Header 4bytes, Footer 4bytes만으로 이미 8bytes이고, 데이터를 저장할 페이로드가 존재하려면 최소 8bytes가 더해져야 한다. (정렬 조건이 8bytes 이므로)
- Prologue Block과 헷갈리지 말 것 :: 간혹 Payload 없이 Header와 Footer만 존재하는 Prologue Block을 보고 이 개념이 헷갈릴 수 있는데, Prologue Block은 Payload를 필요로 하지 않는 유일한, 특수한 블록이라는 점을 기억하자.
void *mm_malloc(size_t size)
{
size_t asize;
size_t extendsize;
char *bp;
/* Ignore spurious requests */
if (size == 0)
return NULL;
/* Adjust block size to include overhead and alignment reqs. */
if (size <= DSIZE)
asize = 2*DSIZE;
else
asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE);
/* Search the free list for a fit */
if ((bp = find_fit(asize)) != NULL){
place(bp, asize);
return bp;
}
/* No fit found. Get more memory and place the block */
extendsize = MAX(asize, CHUNKSIZE);
if ((bp = extend_heap(extendsize/WSIZE)) == NULL)
return NULL;
place(bp,asize);
return bp;
}
size = 사용자가 요청한 메모리 크기
asize = 정렬 요구사항에 맞춘 블록 크기
extendsize = 힙을 확장할 때 필요한 크기
/* Ignore spurious requests */
if (size == 0)
return NULL;
/* Adjust block size to include overhead and alignment reqs. */
if (size <= DSIZE)
asize = 2*DSIZE;
else
asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE);
asize를 계산 - (조건 분기)
size == 0 일 때, NULL 반환size <= DSIZE 일 때, asize = 2*DSIZE로 설정. (2*DSIZE는 최소 블록 크기)else ) size를 8bytes 정렬을 만족하는 크기로 올림하는 계산식 수행asize = DSIZE * ((size+(DSIZE)+(DSIZE))/DSIZE)asize만큼 할당이 가능한 공간 탐색 (find_fit 호출)
/* Search the free list for a fit */
if ((bp = find_fit(asize)) != NULL){
place(bp, asize);
return bp;
}
asize를 할당할 수 있는 공간이 없다면, extend_heap 호출
/* No fit found. Get more memory and place the block */
extendsize = MAX(asize, CHUNKSIZE);
if ((bp = extend_heap(extendsize/WSIZE)) == NULL)
return NULL;
place(bp,asize);
return bp;
}
6-1. First-Fit 방식으로 구현
/* First-fit search */
static void *find_fit(size_t asize)
{
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
}
포인터 bp에 heap_listp (첫 블록의 포인터)할당하고 다음 블록으로 넘어가며 할당 가능한 블록을 찾는다.
6-2. Next-Fit 방식으로 구현
/* Next-fit search */
static void *find_fit(size_t asize)
{
void *bp;
if (last_bp == NULL)
last_bp = heap_listp;
for (bp = last_bp; GET_SIZE(HDRP(bp)) > 0; bp = NEXT_BLKP(bp)) {
if (!GET_ALLOC(HDRP(bp)) && (asize <= GET_SIZE(HDRP(bp)))) {
last_bp = bp;
return bp;
}
}
return NULL; // No fit
}
포인터 bp에 last_bp (마지막으로 할당 연산을 한 포인터)할당하고 다음 블록으로 넘어가며 할당 가능한 블록을 찾는다.
6-3. Best-Fit 방식으로 구현
작성 중,,,
/*
* mm_free - Freeing a block does nothing.
*/
void mm_free(void *ptr)
{
size_t size = GET_SIZE(HDRP(ptr));
PUT(HDRP(ptr),PACK(size,0));
PUT(FTRP(ptr),PACK(size,0));
coalesce(ptr);
}
void *mm_realloc(void *ptr, size_t size) {
void *oldptr = ptr;
void *newptr;
size_t copySize;
newptr = mm_malloc(size);
if (newptr == NULL)
return NULL;
copySize = GET_SIZE(HDRP(oldptr)) - DSIZE;
if (size < copySize)
copySize = size;
memcpy(newptr, oldptr, copySize);
mm_free(oldptr);
return newptr;
}



