ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CSAPP : Allocator 구현(종합 설계, Explict)
    개발/Malloc 2022. 5. 11. 20:07

    1. 명시적 가용 리스트 사용 이유

    앞선 글에서 Implicit Allocator를 설계 했다. 

    블록 할당 시간이 전체 힙 블록의 수에 비례하기 때문에 Implicit 가용 리스트는 범용 할당기에 적합하지 않다.

    (why? : find_fit 함수를 확인하면 블록이 가용하고, 내가 갖고 있는 asize를 담을 수 있으면 할당을 할 수 있는데 이를 확인하려면 전체 힙 블록의 수를 하나씩 확인해가야하고 최악의 경우에는 전체 힙 블록을 다 탐색해야할 수 있다.)

     

    이를 보완한 것이 명시적 가용 리스트(Explicit Free List)이다.

     - 가용 블록들을 명시적 자료구조로 구성

     - 포인터들을 가용 블록의 본체 내에 저장하여 구현

     - 가용 블록 내에 pred(predecessor)와 succ(successor) 포인터를 포함하는 이중 연결 가용 리스트로 구성

      - 이러한 이중 연결 가용 리스트를 사용하면 first fit 할당 시간 --> 가용 블록의 수에 비례하는 것으로 줄임

      단점 : 일반적으로 가용 블록들이 충분히 커서 모든 필요한 포인터 뿐만 아니라 헤더, 추가적으로 풋터까지 포함해야한다.

                 그 결과, 최소 블록 크기가 커지고 잠재적인 내부 단편화 가능성이 커진다

     

    2. Explicit Free List 구현

    1) IDEA

     - 가용 블록끼리 포인터로 연결해 다음 블록 위치를 알 수 있음.

     - 가용 블록 안의 Payload자리에 앞 가용 블록(predecessor block)와 뒷 가용 블록(successor block)의 주소를 저장

     - 하나의 이중 연결 리스트를 만들어서 가용 블록을 연결

     - 다음 가용 블록은 Implicit 방식과 달리 메모리 내에서 물리적으로 연속적이지 않아도 된다.

     - 주소 상으로 인접 블록이 아니더라도 포인터 주소를 이용해서 위치를 찾아간다.

    2) 할당(Allocation)

     - 해당 가용 리스트를 분할한 다음 새 가용 블록을 기존 가용 블록과 링크되었던 이전(prev), 다음(next) 블록과 연결

    3) 가용(Free)

     - 새로 반환된 가용 블록을 free list를 어디에 연결?

       -> LIFO(Last in First Out) 방안

        1) Free list 처음에 새 반환 블록을 넣는다.(Stack)

       -> Address Ordered 방안

        1) Free list 주소 순으로 연결 (이전 블록 주소 < 현재 블록 주소 < 다음 블록 주소)

        2) 단편화 발생 확률은 낮아지나 탐색 시간이 길어진다. (주소 순으로 연속적으로 연결하니 일일이 찾아가면서 해야한다)

     3-1) LIFO(Last in First Out) 방안

      (1) Case 1 : 반환 블록 양쪽이 할당되어 있을 때

      - 해당 블록만 가용 상태로 반환하면 되니 추가 작업이 필요 없고, 연결된 새 가용 블록을 Free list에만 추가해주면 된다.

      (2) Case 2 : 반환 블록 직전이 할당 블록, 직후가 가용 블록일 때

      - 직후 블록을 Free list에서 떼어낸 다음 현재 반환할 블록 뒤로 연결한다.

      - 새로 연결한 블록을 Free list의 root와 연결한다.(Last In 구조)

     (3) Case 3 : 반환 블록 직전이 가용블록, 직후가 할당 블록일 때 

      - 직전 블록을 Free list에서 떼어낸 다음 반환 블록과 연결한다.

      - 새 연결할 블록을 Free list의 root와 연결한다.

     (4) Case 4 : 반환 블록 직전/직후 전부 다 가용 블록일 때

      - 직전 블록/직후

      - 새 블록을 Free list의 root와 연결한다

    3. Malloc 구현 코드 

    memlib.c

    // --------------------- Declaration --------------- //
    extern int mm_init(void); // 최초 가용 블록으로 힙 생성하기
    extern void *mm_malloc (size_t size); // 가용 리스트에서 블록 할당하기
    extern void mm_free (void *ptr); // 블록을 가용 상태로 반환하기
     
     
    /* Private global variables */
    static char *mem_heap;  // 힙의 처음을 가리키는 포인터
    static char *mem_brk; // 힙의 마지막 자리를 가리키는 포인터
    static char *mem_max_addr; // MAX_HEAP의 끝 자리를 나타낸다. 이 이상으로는 할당할 수 없음
     
    /* *mem_init - Initilaize the memory system model */
    // 최초 가용 블록으로 힙 생성하기
     
    void mem_init(void)
    {
        mem_heap = (char *)Malloc(MAX_HEAP); // MAX_HEAP 크기 만큼 할당
        mem_brk = (char *)mem_heap; // 힙의 처음 가리키는 포인터로 brk 정의 --> 마지막 자리를 가리킬 것이다.
        mem_max_addr = (char *)(mem_heap + MAX_HEAP); // 힙의 처음을 가리키는 포인터 + MAX_HEAP size이므로 MAX_HEAP의 끝 자리를 나타냄
    }
     
    /*
     * 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; // 전에 있던 brk값으로 mem_sbrk의 리턴 값이 된다.
        // incr(용량)이 들어왔을 때, 음수거나, MAX_HEAP을 초과하면 오류 메시지 반환
        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;
    }​

    mm.c

    mm파일을 수정하여 Malloc을 구현하는 것이다.

    먼저 코드 전체에서 사용할 상수와 매크로를 정의

    매크로란 코드에서 반복적으로 선언(사용)되는 숫자나 함수를 미리 정의하여 의미가 서로 다른 것이 섞여서 사용되면 의미가 헷갈리기 때문에 그 부분을 방지해줌. 또한, 코드가 길어지고 복잡해졌을 때 코드 입력 실수를 줄일 수 있다.

    #define은 컴파일 직전에 처리되므로 전처리기 과정을 거치면 매크로가 정의된다.
    보통 전처리기는 반복되는 값이나 작업을 미리 정의할 때 사용하며 컴파일 옵션 설정이나 조건부 컴파일도 가능하다.

    - 매크로 선언
    #define 매크로명 매크로하고자 하는 대상
    - 기존에 선언된 매크로의 이름을 재정의
    #define 새 매크로명 기존 매크로명
    - 매크로 해제
    #undef 매크로 이름

    - 매크로

    /* single word (4) or double word (8) alignment */
    #define ALIGNMENT 8
    
    /* rounds up to the nearest multiple of ALIGNMENT */
    #define ALIGN(size) (((size) + (ALIGNMENT-1)) & ~0x7)
    
    /* 메모리 할당 시 기본적으로 header와 footer를 위해 필요한 더블워드만큼의 메모리 크기 */
    #define SIZE_T_SIZE (ALIGN(sizeof(size_t)))
    
    /* Basic constants and macros */
    #define WSIZE 4 /* Word and header/footer size (bytes) */
    #define DSIZE 8 /* Double word size (bytes) */
    #define MINIMUM 16 /* Initial Prologue block size, header, footer, PREC, SUCC */
    #define CHUNKSIZE (1<<12) /* Extend heap by this amount : 4096bytes -> 4k(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))) // (char*)(bp) + GET_SIZE(지금 블록의 헤더값)
    #define PREV_BLKP(bp) ((char *)(bp) - GET_SIZE(((char *)(bp) - DSIZE))) // (char*)(bp) - GET_SIZE(이전 블록의 풋터값)
    
    /* Free List 상에서의 이전, 이후 블록의 포인터를 리턴한다. */
    #define PREC_FREEP(bp) (*(void**)(bp))          // 이전 블록의 bp
    #define SUCC_FREEP(bp) (*(void**)(bp + WSIZE))  // 이후 블록의 bp
    
    /* define searching method for find suitable free blocks to allocate*/
    
    /* global variable & functions */
    static char* heap_listp = NULL; // 항상 prologue block을 가리키는 정적 전역 변수 설정
    static char* free_listp = NULL; // free list의 맨 첫 블록을 가리키는 포인터​

    - int mm_init(void)

     ❖ Explicit allocator 의 초기 힙은 6word의 메모리를 가진다.

    /* 
     * mm_init - initialize the malloc package.
     */
    
    int mm_init(void)
    {
    
        /* 메모리에서 6words를 가져오고 이걸로 빈 가용 리스트 초기화 */
         /* padding, prol_header, prol_footer, PREC, SUCC, epil_header */
        if ((heap_listp = mem_sbrk(6*WSIZE)) == (void*)-1){
            return -1;
        }
        PUT(heap_listp,0);                          /* Alignment padding */
        PUT(heap_listp + (1*WSIZE), PACK(MINIMUM,1)); /* Prologue header */
        PUT(heap_listp + (2*WSIZE), NULL); // prologue block안의 PREC 포인터 NULL로 초기화
        PUT(heap_listp + (3*WSIZE), NULL); // prologue block안의 SUCC 포인터 NULL로 초기화
        PUT(heap_listp + (4*WSIZE), PACK(MINIMUM,1));  /* Prologue footer */
        PUT(heap_listp + (5*WSIZE), PACK(0,1));     /* Epilogue header */
        
        free_listp = heap_listp + 2*WSIZE; //free_listp를 탐색의 시작점으로 둔다
    
        /* Extend the empty heap with a free block of CHUNKSIZE bytes */
        // 그 후 CHUNKSIZE만큼 힙을 확장해 초기 가용 블록을 생성한다
        if (extend_heap(CHUNKSIZE/WSIZE)==NULL)
            return -1;
        return 0;
    }

    - static void* coalesce(void* bp)

    // coalesce(bp) : 해당 가욕 블록을 앞뒤 가용 블록과 연결하고 연결된 가용 블록의 주소를 리턴한다.
    
    static void *coalesce(void *bp){
        // 직전 블록의 footer, 직후 블록의 header를 보고 가용 여부 확인
        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));
    
        // Case 1 : 앞, 뒷 블록 모두 할당 -> 해당 블록만 free list에 넣어준다
        
        // Case 2 : 앞 블록 할당, 뒷 블록 가용
        if (prev_alloc && !next_alloc){ 
            removeBlock(NEXT_BLKP(bp)); // free 상태였던 뒷 블록 free list에서 제거
            size += GET_SIZE(HDRP(NEXT_BLKP(bp))); // 다음 블록의 헤더만큼 사이즈 추가
            PUT(HDRP(bp),PACK(size,0)); // 헤더 갱신
            PUT(FTRP(bp), PACK(size,0)); // 푸터 갱신
        }
        //Case 3 : 앞 블록 가용, 뒷 블록 할당
        else if(!prev_alloc && next_alloc){  
            removeBlock(PREV_BLKP(bp)); // free 상태였던 앞 블록 free list에서 제거
            size+= GET_SIZE(HDRP(PREV_BLKP(bp))); // 이전 블록의 헤더만큼 사이즈 추가
            bp = PREV_BLKP(bp);
            PUT(HDRP(bp), PACK(size, 0));
            PUT(FTRP(bp), PACK(size, 0)); 
    
        }
        //Case 4 : 앞 블록 가용, 뒷 블록 가용
        else if(!prev_alloc && !next_alloc) { 
            removeBlock(NEXT_BLKP(bp)); // free 상태였던 뒷 블록 free list에서 제거
            removeBlock(PREV_BLKP(bp)); // free 상태였던 앞 블록 free list에서 제거
            size+= GET_SIZE(HDRP(PREV_BLKP(bp))) + GET_SIZE(FTRP(NEXT_BLKP(bp))); // 이전 블록 헤더, 다음 블록 푸터 까지로 사이즈 늘리기
            bp = PREV_BLKP(bp);
            PUT(HDRP(bp), PACK(size, 0));
            PUT(FTRP(bp), PACK(size, 0)); 
            
            
        }
        // 연결된 새 가용 블록을 free list에 추가한다
        putFreeBlock(bp);
    
        return bp;
    }

    - void putFreeBlock(void* bp)

     

    순서(Sequence)

    1) 현재 블록

    PREC : NULL

    SUCC : NULL

    free_listp : PREC의 위치, 시작점이라고 생각

    2) 가용 블럭 추가됨(Last In)

    추가된 블록을 기준으로 생각해보자

    PREC(현재 bp) : NULL (현재 앞에 가리킬 것이 없기 때문에)

    SUCC(현재 bp) : 다음 블록(원래 이전에 있던 것)의 bp(블록 포인터)를 가리켜야함

    PREC(이전 bp=free_listp) : 현 블록의 bp를 가리켜야함

    다음에 들어올 것을 감안하여 

    free_listp위치도 현 bp값으로 바꿔줘야함.

     

    이를 위해서, 이중 포인터 개념 필요

    참고 : https://velog.io/@letsbebrave/c-%EC%9D%B4%EC%A4%91%ED%8F%AC%EC%9D%B8%ED%84%B0

    무언가 형식 지정자 때문에 필요한 것 같다.

    /*
        putFreeBlock(bp) : 새로 반환되거나 생성된 가용 블록을 free list의 첫 부분에 넣는다.
    */
    
    // Last in : 선입 구조
    // free_listp : 뒷 블록의 bp
    // bp : 현 블록의 bp
    void putFreeBlock(void* bp){
        
        SUCC_FREEP(bp) = free_listp; // 뒷블록의 bp값 = free_listp
        PREC_FREEP(bp) = NULL;       // 앞블록의 bp값 = NULL(앞블록은 선입 구조이기 때문에 없음)
        PREC_FREEP(free_listp) = bp; // 뒷블록 기준-> 앞블록(현블록)의 bp값 = bp
        free_listp = bp;             // free_listp는 다음에 들어올 것을 염두에 두고 bp로 갱신
    }

    - void removeBlock(void *bp)

    /*
        removeBlock(bp) : 할당되거나 연결되는 가용 블록을 free list에서 없앤다.
    */
    
    void removeBlock(void *bp){
    
        //free list의 첫번째 블록을 없앨 때
        if (bp == free_listp){
            PREC_FREEP(SUCC_FREEP(bp)) = NULL; // 앞에 것의 PREC이 가리키는 값을 NULL로 바꿔준다.
            free_listp = SUCC_FREEP(bp); //free_listp의 값을 뒤의 bp로 바꿔준다
        }
        // free list 안에서 없앨 때
        else{
    
            SUCC_FREEP(PREC_FREEP(bp)) = SUCC_FREEP(bp); //앞 블록의 SUCC가 없애주는 블록의 SUCC값을 가리키도록 한다.
            PREC_FREEP(SUCC_FREEP(bp)) = PREC_FREEP(bp); //뒷 블록의 PREC가 없애주는 블록의 PREC값을 가리키도록 한다.
        }
    }

    bp를 인자로 받았을 때

    1) free list의 첫번째 블록이라면

    - 앞에 것의 PREC이 가리키는 값을 NULL로 바꿔준다.

    - free_listp의 값을 뒤의 bp로 바꿔준다

    2) free list의 첫번째 블록이 아니라면

     - 앞 블록의 SUCC가 없애주는 블록의 SUCC값을 가리키도록 한다.

     - 뒷 블록의 PREC가 없애주는 블록의 PREC값을 가리키도록 한다.

     

    - static void* find_fit(size_t asize)

    static void *find_fit(size_t asize)
    {
        /* First-fit search */
        void *bp;
        // free list에서 가용 블럭을 차례로 확인한다. 맨 뒤의 프롤로그 블록이 유일하게 할당되었으므로 만나면 탐색 종료 
        for (bp = free_listp; GET_ALLOC(HDRP(bp)) != 1; bp = SUCC_FREEP(bp)) { 
                
            if (asize <= GET_SIZE(HDRP(bp))) {
             //이 블록이 내가 갖고 있는 asize를 담을 수 있으면
              return bp; // 블록 위치 바로 리턴
            }
        }
        return NULL; /* No fit */
    
    }

    First fit 방식으로 구현.

    Free list에서 유일한 할당 블록은 맨 뒤의 프롤로그 블록이므로, 할당 블록을 만났다는 뜻은 모든 Free list 노드를 다 훑은 것으로 봄

    (== 종료 조건)

     

    - static void place(void* bp, size_t asize)

    static void place(void *bp, size_t asize)
      // 들어갈 위치를 포인터로 받는다. (find fit에서 찾은 bp) 크기는 asize로 받음.
    {
        size_t csize = GET_SIZE(HDRP(bp));
        // 현재 있는 블록 사이즈
    
        removeBlock(bp);
        
        if ((csize - asize) >= (2*DSIZE)) {
            // 현재 블록 사이즈 안에서 asize를 넣어도 2*DSIZE(헤더와 풋터를 감안한 최소 사이즈)만큼 남으면
            // 다른 data를 넣을 수 있음
            PUT(HDRP(bp), PACK(asize, 1));
            // 헤더 위치에 asize만큼 넣고 1(alloc)로 상태 변환. 원래 헤더 사이즈에서 지금 넣으려고 하는 사이즈(asize)로 갱신.(자르는 효과)
            PUT(FTRP(bp), PACK(asize, 1));
            // 풋터도 변경
            bp = NEXT_BLKP(bp);
            // regular block만큼 하나 이동해서 bp 위치 갱신
            PUT(HDRP(bp), PACK(csize-asize, 0));
            // 나머지 블록(csize-asize) 다 가용하다(0)하다라는 걸 다음 헤더에 표시
            PUT(FTRP(bp), PACK(csize-asize, 0));
            // 풋터에도 표시
    
            putFreeBlock(bp);
            // free list 첫번째에 분할된 블럭을 넣는다.
        }
        else { // 현재 블록 사이즈 안에서 asize를 넣으면 2*DSIZE보다 작으면 분할할 필요 없음
            PUT(HDRP(bp), PACK(csize, 1));
            PUT(FTRP(bp), PACK(csize, 1));
        }
    }

    직접적으로 힙에 할당 메모리를 넣는 작업

    1) 할당할 수 있는 free 블록을 Free list에서 없애주고

    2) 할당 후 분할이 되었다면, Free list 첫번째에 분할된 블럭을 넣어야한다.

     

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

    /*
     * mm_realloc - Implemented simply in terms of mm_malloc and mm_free
     */
    // 조정할 블록 위치와 요청 사이즈를 인자로 받는다.
    void *mm_realloc(void *ptr, size_t size)
    {
        void *oldptr = ptr; // 크기를 조절하고 싶은 힙의 시작 포인터
        void *newptr;       // 크기 조절 뒤의 새 힙의 시작 포인터
        size_t copySize;    // 복사할 힙의 크기
        
        newptr = mm_malloc(size);
        if (newptr == NULL) // 요청 사이즈가 0보다 작거나 같으면 반환을 한다.
          return NULL;
    
        // copySize = 조절하고 싶은 힙의 크기
        copySize = GET_SIZE(HDRP(oldptr));  
    
        // 원래 메모리 크기보다 적은 크기를 realloc하면 크기에 맞는 메모리만 할당되고 나머지는 안된다.
        if (size < copySize)
          copySize = size;
        memcpy(newptr, oldptr, copySize); // newptr에 oldptr를 시작으로 copySize만큼의 메모리 값을 복사한다.
        mm_free(oldptr);    // 기존의 힙을 반환한다.
        return newptr;
    }

    realloc 함수는 malloc으로 할당 받은 메모리 영역을 size를 변경하여 재할당받는 역할을 한다.

    주의할 점은 할당된 블록을 늘리거나 줄이는 것이 아닌 새롭게 원하는 size를 할당 받고 전에 것을 free하는 형식이다.

     

    출처 :

    https://woonys.tistory.com/entry/%EC%A0%95%EA%B8%80%EC%82%AC%EA%B4%80%ED%95%99%EA%B5%90-43%EC%9D%BC%EC%B0%A8-TIL-Explicit-malloc-%EC%A0%95%EB%A6%AC-%EB%B0%8F-%EA%B5%AC%ED%98%84-review?category=961590 

     

    https://github.com/dkkim0122/malloc-lab/blob/main/mm.c

     

    '개발 > Malloc' 카테고리의 다른 글

    CSAPP : Allocator 구현(종합 설계)  (0) 2022.05.08
    CSAPP : 동적 메모리 할당 (9.9)  (0) 2022.05.07
Designed by Tistory.