컴퓨터 시스템 : CSAPP 9장 정리 - 9.9.12 종합 설계 : 간단한 할당기의 구현 Part.1
CSAPP 9.9.12 '간단한 할당기의 구현(Putting It Together: Implementing a Simple Allocator)'할당기를 만드는 것은 까다로운 작업이다. 블록 포맷, 가용 리스트 포맷, 배치 정책, 분할 정책, 통합 정책 등 다양한 선
www.gowoong.com
이전 포스팅에 이어서 묵시적 가용 리스트를 사용하는 할당기를 구현해 보겠다.
CSAPP 9.9.12 '간단한 할당기의 구현(Putting It Together: Implementing a Simple Allocator)'
블록 반환과 연결 (Freeing and Coalescing)
동적 메모리 관리에서는 malloc으로 할당한 블록을 다 쓴 뒤, free로 반환해야 한다. 반환된 블록은 다른 할당 요청에 재사용할 수 있어야 한다. 그러나 반환된 블록 주변에도 가용 블록이 있을 수 있기 때문에, 가능하다면 인접한 가용 블록들과 병합(coalescing) 해서 더 큰 가용 블록을 만든다. 이렇게 하면 외부 단편화(external fragmentation) 를 줄일 수 있다.
mm_free 함수
mm_free(void *bp) 함수는 주어진 블록 포인터 bp가 가리키는 블록을 반환한다.
구체적인 동작은 다음과 같다:
- bp가 가리키는 블록의 헤더를 찾아 블록 크기를 얻는다.
- 해당 블록의 헤더와 풋터를 "가용 상태(allocated = 0)"로 변경한다.
- coalesce(bp)를 호출하여 인접한 가용 블록들과 병합한다.
PUT(HDRP(bp), PACK(size, 0));
PUT(FTRP(bp), PACK(size, 0));
void mm_free(void *bp)
{
size_t size = GET_SIZE(HDRP(bp));
PUT(HDRP(bp), PACK(size, 0));
PUT(FTRP(bp), PACK(size, 0));
coalesce(bp);
}
coalesce 함수
coalesce(void *bp) 함수는 현재 블록 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);
}
return bp;
}
병합 흐름 요약
- 병합 전후에 항상 HDRP와 FTRP를 일관되게 수정한다.
- 에필로그 블록은 항상 할당 상태이기 때문에, coalesce는 힙 경계를 넘어가는 경우를 별도로 신경 쓸 필요가 없다.
- 프롤로그 블록도 항상 할당 상태이므로, 힙의 시작 부분에서도 병합 처리를 간단히 할 수 있다.
블록 할당
- mm_malloc는 요청된 size를 내부 관리용 오버헤드(헤더와 풋터)와 정렬을 반영하여 asize로 조정한다.
- 가용 리스트를 순회하면서 find_fit으로 첫 번째로 맞는 블록을 찾는다.
- 맞는 블록이 있으면 place를 호출해 배치한다. 필요시 블록을 분할(split)한다.
- 맞는 블록이 없다면 힙을 확장(extend_heap)한 뒤 새 가용 블록에 place한다.
void *mm_malloc(size_t size)
{
size_t asize; /* Adjusted block size */
size_t extendsize; /* Amount to extend heap if no fit */
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;
}
애플리케이션이 kk 바이트짜리 블록을 요청하면, 할당기는 가용 리스트(free list) 를 순회하면서 요청을 만족할 수 있는 충분히 큰 블록을 찾는다.
할당기가 어떤 블록을 선택하는지는 배치 정책(placement policy) 에 따라 달라진다.
대표적인 배치 정책은 다음과 같다:
- First Fit (최초 적합)
가용 리스트의 시작부터 순차적으로 탐색하여, 요청을 만족하는 처음 만나는 가용 블록을 선택한다. - Next Fit (다음 적합)
이전 탐색이 끝난 지점부터 탐색을 계속하여, 요청을 만족하는 가용 블록을 찾는다. 항상 리스트 처음부터 탐색하지 않기 때문에 효율이 더 나을 수 있다. - Best Fit (최적 적합)
가용 리스트 전체를 탐색해서, 요청을 만족하는 블록 중 가장 크기가 작은 블록을 선택한다. 외부 단편화를 줄일 수 있지만, 탐색 비용이 크다.
find_fit 함수 구현 (책 예시)
책에서는 First Fit 정책을 사용하는 예시로 find_fit 함수를 이렇게 작성한다:
static void *find_fit(size_t asize)
{
/* 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 */
}
place 함수 구현 (책 예시)
찾은 블록을 배치할 때는 요청 크기보다 블록이 충분히 크면 분할(splitting) 을 수행한다.
분할 가능한 경우, 앞부분에 요청 크기만큼 할당 블록을 만들고, 나머지는 가용 블록으로 남긴다.
분할이 불가능할 정도로 작은 경우에는 전체 블록을 할당해버린다.
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));
}
}
전체 코드
/* Basic constants and macros */
#define WSIZE 4 /* Word and header/footer size (bytes) */
#define DSIZE 8 /* Doubleword size (bytes) */
#define CHUNKSIZE (1<<12) /* Extend heap by this amount (bytes) */
#define MAX(x, y) ((x) > (y) ? (x) : (y))
/* Pack a size and allocated bit into a word */
#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))
/* Read 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)))
static char *heap_listp; /* Pointer to first block */
/* Function prototypes */
static void *extend_heap(size_t words);
static void *coalesce(void *bp);
static void *find_fit(size_t asize);
static void place(void *bp, size_t asize);
/* Initialize the heap */
int mm_init(void)
{
if ((heap_listp = mem_sbrk(4*WSIZE)) == (void *)-1)
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);
if (extend_heap(CHUNKSIZE/WSIZE) == NULL)
return -1;
return 0;
}
/* malloc: Allocate a block with at least size bytes of payload */
void *mm_malloc(size_t size)
{
size_t asize; /* Adjusted block size */
size_t extendsize; /* Amount to extend heap if no fit */
char *bp;
if (size == 0)
return NULL;
if (size <= DSIZE)
asize = 2*DSIZE;
else
asize = DSIZE * ((size + (DSIZE) + (DSIZE-1)) / DSIZE);
if ((bp = find_fit(asize)) != NULL) {
place(bp, asize);
return bp;
}
extendsize = MAX(asize, CHUNKSIZE);
if ((bp = extend_heap(extendsize/WSIZE)) == NULL)
return NULL;
place(bp, asize);
return bp;
}
/* free: Free a block */
void mm_free(void *bp)
{
size_t size = GET_SIZE(HDRP(bp));
PUT(HDRP(bp), PACK(size, 0));
PUT(FTRP(bp), PACK(size, 0));
coalesce(bp);
}
/* coalesce: Boundary tag coalescing. Return ptr to coalesced block */
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);
}
return bp;
}
/* extend_heap: Extend heap with free block and return its block pointer */
static void *extend_heap(size_t words)
{
char *bp;
size_t size;
size = (words % 2) ? (words+1) * WSIZE : words * WSIZE;
if ((long)(bp = mem_sbrk(size)) == -1)
return NULL;
PUT(HDRP(bp), PACK(size, 0)); /* Free block header */
PUT(FTRP(bp), PACK(size, 0)); /* Free block footer */
PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); /* New epilogue header */
return coalesce(bp);
}
/* find_fit: Find a fit for a block with asize bytes */
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;
}
/* place: Place block of asize bytes at start of free block bp */
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));
}
}
'크래프톤 정글 (컴퓨터 시스템: CSAPP) > 9장 가상 메모리' 카테고리의 다른 글
컴퓨터 시스템 : CSAPP 9장 정리 - 9.9.13 명시적 가용 리스트 Part.2 (1) | 2025.04.27 |
---|---|
컴퓨터 시스템 : CSAPP 9장 정리 - 9.9.13 명시적 가용 리스트 Part.1 (1) | 2025.04.27 |
컴퓨터 시스템 : CSAPP 9장 정리 - 9.9.12 종합 설계 : 간단한 할당기의 구현 Part.1 (0) | 2025.04.27 |
컴퓨터 시스템 : CSAPP 9장 정리 - 9.9 동적 메모리 할당 9.9.6~9.9.11 (0) | 2025.04.25 |
컴퓨터 시스템 : CSAPP 9장 정리 - 9.9 동적 메모리 할당 9.9.1~9.9.5 (1) | 2025.04.24 |