본문 바로가기

CS(ComputerScience)

명시적 가용 리스트 Explicit Free List

728x90

명시적 가용 리스트 Explicit Free List

요소를 저장하기 위해 명시적으로 메모리 공간(Array, Linked List) 할당

 

explicit free list

장점 : 검색 속도가 빠르고 메모리 할당, 해제 쉬움

단점 : 내부 단편화 발생


free_list를 가리키는 2중 포인터 설정

매크로 설정.

// free block 들의 정보를 모아둔 리스트 정보
// SUCC가 PRED보다 많이 쓰이므로 데이터를 접근하기 쉽도록 HEADER와 더 가까이 배치
#define NEXT_FREE(bp) (*(void **)(bp))         /* Next free block의 시작주소 */
#define PREV_FREE(bp) (*(void **)(bp + WSIZE)) /* Prev free block의 시작주소 */
 
static char *free_listp; /* 가용 블록 리스트의 시작 */
 

free block들의 정보를 모아둔 리스트 헤더 : free_listp

 


 

📄 mm_init

/*
 * mm_init - initialize the malloc package.
 */
int mm_init(void)
{
    /* Create the initial empty heap */
    if ((heap_listp = mem_sbrk(6 * WSIZE)) == (void *)-1) /* 메모리 할당 오류가 발생했으면 */
        return -1;
    PUT(heap_listp, 0);                                /* Alignment padding */
    PUT(heap_listp + (1 * WSIZE), PACK(2 * DSIZE, 1)); /* Prologue, heap의 정렬 & heap의 sential역할(경계태그) */
    /* 왜 NEXT를 앞으로? payload 자리를 찾아가는 코드로 구성되어 있기 때문에 해당 자리에서 바로 찾을 수 있는 곳에 NEXT를 둬서 최대한의 동작을 줄임 */
    PUT(heap_listp + (2 * WSIZE), PACK(0, 0));               /* 프롤로그 NEXT NULL로 초기화. */
    PUT(heap_listp + (3 * WSIZE), PACK(0, 0));               /* 프롤로그 PREV NULL로 초기화 */
    PUT(heap_listp + (4 * WSIZE), PACK(2 * DSIZE, 1)); /* Prologue footer */
    PUT(heap_listp + (5 * WSIZE), PACK(0, 1));         /* Epilogue header */

    free_listp = heap_listp + DSIZE; /* 첫 가용블록의 payload */
    // free_listp = heap_listp + 2 * DSIZE;

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

🔎 prologue header & footer에 2 * DSIZE를 할당한 이유

연결을 위한 여분의 공간이 필요.

 

🔎 next_free를 prev_free보다 header에 가깝게 배치한 이유

보통 다음 가용 블록을 탐색하는 일이 이전 가용 블록을 탐색하는 일보다 많기때문에 next(succ)를 prev보다 먼저 배치한 코드가 많다.

 


연결 리스트에 blcok 추가

splice_in

/* LIFO - 반환되거나 분할로 생긴 가용 블록을 가용리스트 가장 앞에 추가 */
static void splice_in(void *bp)
{
    PREV_FREE(bp) = NULL; // 이전 블록은 없으니 NULL 할당
    NEXT_FREE(bp) = free_listp; // 현재 블록의 다음 가용 블록의 free_listp가 가리키는 포인터 할당
    PREV_FREE(free_listp) = bp; // 가용 블록의 이전 가용 블록을 현재 블록을 가리키는 포인터 할당
    free_listp = bp; // free_listp가 가리키는 블록으로 현재 블록을 가리키는 포인터 할당
}

root : free_listp로 가용 연결리스트의 맨 앞을 가리킨다. 링크드 리스트의 head같은 역할.

null : 가용 연결 리스트에 맨 마지막을 의미한다. 링크드 리스트의 tail같은 역할.

 

LIFO 방식으로 가용 연결 리스트 맨앞에 새로운 가용블록 추가

 


 

연결 리스트에 block 제거

splice out

/* 가용 블록을 free list에서 삭제 */
void splice_out(void *bp)
{
    if (bp) {
        // 이전 가용블록 없음
        if (PREV_FREE(bp)) {
            NEXT_FREE(PREV_FREE(bp)) = NEXT_FREE(bp);
        }
        else {
            free_listp = NEXT_FREE(bp);
        }
        if (NEXT_FREE(bp) != NULL) {
            // 이후 가용 블록 없음
            PREV_FREE(NEXT_FREE(bp)) = PREV_FREE(bp);
        }
    }
}

만약, bp 이후 가용 블록이 없으면, NEXT_FREE(bp)가 NULL값이 되므로 오류가 발생할 수 있다.

따라서 전후로 가용블록이 있냐 없냐 조건에 따라 혹시 모를 예외처리를 해줘야 한다.


가용 블록 연결리스트 업데이트

 

📄 coalesce 함수

static void *coalesce(void *bp)
{
	...
    // Case 1
	// 가용 블록 연결리스트 처리해 줄 작업 없음.

    // Case 2
    if (prev_alloc && !next_alloc) {
        // 이전 블록 할당, 다음 블록 free
        // 다음 블록 header에 접근
        splice_out(NEXT_BLKP(bp)); // 미리 제거해 혹시 모를 포인터로 인한 오류 방지
		...
    }

    // Case 3
    else if(!prev_alloc && next_alloc) {
        // 이전 블록 free, 다음 블록 할당
        // 이전 블록 footer에 접근
        splice_out(PREV_BLKP(bp));
		...
    }

    // Case 4
    else if(!prev_alloc && !next_alloc) {
        // 이전 블록 free, 다음 블록 free
        // 이전 블록 footer와 다음 블록 header에 접근
        splice_out(NEXT_BLKP(bp));
        splice_out(PREV_BLKP(bp));

		...
    }

    splice_in(bp);

    return bp;
}

 

📄 place 함수

static void place(void *bp, size_t asize)
{
	...
    splice_out(bp);                    /* 사용할 가용블록을 가용리스트에서 없애기 */
    if ((csize - asize) >= (2 * DSIZE)) /* 분할의 가치가 있음 */
    {
		...
        splice_in(bp); /* 분할하고 남은 가용블록 가용리스트에 추가 */
		...
    }
    else /* 남은 가용 블록의 크기가 2*DSIZE보다 작으면 */
		...
}

 

🔎 가용블록 제거 후, 가용블록 추가하는 이유

가용 블록 연결리스트에서 임의의 가용블록을 제거한 뒤, 추가 시키는 업데이트 작업을 진행할때는 예기치 못한 오류를 방지해야 한다.

가용 블록 연결리스트에서 정보를 업데이트 할 때는 반드시 splice_out(노드 제거)을 먼저 수행하고 splice_in(노드 추가)을 수행하는 것이 안전하다. 만약, 이 과정을 반대로 진행한다면, splice_out한 노드가 아직 가용블록 연결 리스트에 남아있지 않은 상태에서 splice_in을 시도하게 되어 노드가 중복되어 추가될 가능성이 있다.


first fit : 가용 블록 연결 리스트를 header부터 탐색해 메모리 할당 가능한 가용블록 발견시 메모리 할당.

2023.04.11 - [CS(ComputerScience)] - Memory allocation using explicit free block tracking and first-fit method


공부 자료 : 컴퓨터 시스템 Computer Systems: A Programmer's Perspective by Randal E. Bryant David R. O'Hallaron 9장 가상 메모리