[Week5] 0215.5

안나경·2024년 2월 15일

크프정 일상

목록 보기
36/109

2시 ~ 3시 10분? 코어

가용 리스트 조작 매크로

...

#define WSIZE	4	// word와 헤더, 풋터 사이즈.
#define DSIZE	8	 // 더블 워드 사이즈.
#define CHUNKSIZE (1<<12)	// 초기 가용 블록과 힙 확장을 위한 기본 크기.

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

#define PACK(size, alloc) ((size) | (alloc)) 
// 크기와 할당 비트를 통합. 헤더와 풋터에 저장할 수 있는 값을 리턴함.

#define GET(p)	(*(unsigned int *)(p)) // 인자 p가 참조하는 워드를 읽어서 리턴. 
// p가 가리키는 메모리 위치에서 4바이트 워드를 읽어서 리턴.
#define PUT(p, val) (*(unsigned int *)(p) = (val)) 
// 인자 p가 가리키는 워드에 val을 저장.

#define GET_SIZE(p)	(GET(p) & ~0x7) // 주소 p에 있는 헤더 또는 풋터의 size를 리턴.
#define GET_ALLOC(p) (GET(p) & 0x1) // 주소 p에 있는 헤더 또는 풋터의 할당 비트를 리턴.

// bp는 블록 포인터.
#define HDRP(bp)	((char *)(bp) - WSIZE) // 헤더를 가리키는 포인터를 리턴.
#define FTRP(bp) 	((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE) 
// 풋터를 가리키는 포인터를 리턴.

#define NEXT_BLKP(bp)	((char *)(bp) + GET_SIZE(((char *)(bp) - WSIZE))) 
// 다음 블록 포인터를 리턴.
#define PREV_BLKP(bp) 	((char *)(bp) - GET_SIZE(((char *)(bp) - DSIZE))) 
// 이전 블록 포인터를 리턴.

T님이 매크로 부터 설명.

1바이트는 8비트라,
4바이트는 32비트.

32개의 0000... 2진수로 채워져있고,

8배수로 저장하기때문에
모든 size는
11000... 즉, 뒤에 세자리는 무조건 0.
이것이 일종이 내부 단편화인데,
노는 값이니까
맨 마지막은 할당, 비할당 여부를 표기.

1이면 할당, 0이면 비할당.

그래서 PACK에서 size | alloc 이 가능한것이다.
비트 연산자로 or이기 때문에,

11000, 1이면

둘 중 하나라도 1이면 1이기때문에
11001 식으로 저장이 되기때문이다.

같은 원리로 GET_SIZE, GET_ALLOC이 가능.
(SIZE는 앞만 가져오면 되고,
ALLOC은 뒤만 가져오니까.)

그리고 GET_SIZE를 할때
& ~0x7을 쓰는 이유는

0x7 = 00111.... 이기때문에
~0x7 = 11000... 이 되어서
and 이기때문에,

뒤에 세자리를 0으로 만들어주는 효과가 있기때문이다.

...

그래서
기존 bp, ptr은
헤더 바로 다음 위치이기때문에

HDRP는 헤더만큼의 사이즈 앞으로 이동했기때문에
-WSIZE고,
FTRP는 현재 위치 + 지금 사이즈 - DSIZE인이유가

지금 사이즈가 헤더+풋터+가용 블록 이기때문에
현재위치(헤더 뒤) + (가용블록) (풋터) (헤더) 위치가 되어버리기때문에
풋터로 가려면 WSIZE를 두번 빼줘야해서 DSIZE를 뺀다.

NEXT BLKP는 현재 위치 -헤더크기 ==> 헤더 위치가 되어
현재 블록 사이즈인 SIZE를 얻어서
bp+SIZE => 다음 블록 위치가 되고

PREV BLKP는
(헤더)(이전 블록 SIZE)(풋터)(헤더) 현재 위치 (SIZE) (풋터)
인데..
현재 위치에서 DSIZE를 빼면
이전 블록 풋터에서 SIZE를 얻어다가
그 SIZE만큼 현재 위치에서 빼기때문에

이전 블록 헤더 다음에 위치하게 된다...

...

...
설명 순서는 이렇지 않았고

  • mem_brk를 따로 만든다.
    (아예 여기서 malloc을 쓴다.)
    진짜 OS를 건드린다기보단 놀이터를 만드는 것이다.
    그래서 (시작 포인터) 현재 brk (max_addr)로 이루어졌고
    MAX_HEAP이라는 내장 값을 쓴다...

  • 그래서 init을 하면
    프롤로그 블록 등을 만드는데
    이때 앞에 8/1이라는게 헤더에 담겨져있는데
    정확히는.... 하고 32비트짜리 이진수가 담긴 컴퓨터의 민낯(?) 과
    가용 리스트 조작 매크로 설명.

  • 조작 매크로를 설명하려면 이전 블록과의 연관도 설명하기 위해 아마 extend_heap까지 겸사 설명.
    본래 ptr은 init이 끝나면
    프롤로그 블록 사이에 있지만,
    extend_heap까지 하면 이제 새로 할당한 free블록
    헤더 다음에 있음.

...

/* 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)


#define SIZE_T_SIZE (ALIGN(sizeof(size_t)))

이것도 추가적으로 설명하셨는데
ALIGN이라는 수식이 padding 임을 이진수로 설명.
(아까 그 & ~0x7과 같은 원리.)

SIZE_T_SIZE는 size_t가 4바이트고
그걸 정렬한거라 8바이트라는 상수값이 된다는 얘기도 함.

SIZE가 가용 블록 크기만 말하는건지, 헤더와 풋터를 포함하는 건지 잠깐 토론이 있었음.

mm_init

새로 힙 만들어줌.

힙에 있는 brk를
sbrk (break point를 set break point 해줌) 함수로 올려줌.

그래서 과정을 전반적으로 설명.

만약 heap이 용량이 충분한지 확인하고,

맨 앞에 padding 용 하나.
그 다음 프롤로그 헤더.
프롤로그 풋터.
에필로그 헤더를 넣고....

extend_heap으로 사용 가능
추가 블록을 넣는 거라 이해하면 좋다고하는데

 * mm_init - initialize the malloc package.
 */
int mm_init(void)
{
    	// 만약 현재 4배의 더블 워드를 더한다면 죽는다고 선언할 경우 실패.
	if((heap_listp = mem_sbrk(4*WSIZE)) == (void *)-1)
    	return -1;
    
    
    // padding 정렬용.
    PUT(heap_listp, 0);
    // 프롤로그 헤더.
    PUT(heap_listp + (1*WSIZE), PACK(DSIZE, 1));
    // 프롤로그 풋터.
    PUT(heap_listp + (2*WSIZE), PACK(DSIZE, 1));
    // 에필로그 헤더.
    PUT(heap_listp + (3*WSIZE), PACK(0,1));
    heap_listp += (2*WSIZE);
    
    // 다 배정했으니 heap의 크기를 늘려놓겠다.
    if(extend_heap(CHUNKSIZE/WSIZE) == NULL)
    	return -1;
    return 0;
}

코드 형식이 이런지라
if문 안에만 있는데 그럼
init할때마다 실제로 넣어주는건지
아니면 늘릴 수 있는지 여부만 확인하는건지 궁금했는데
아예 함수 호출이라 if문 안에서 해결하는
짧은 코드 케이스라고.
(실제로 넣어준다고 보면 된다.)

그래서 만약 힙 확장이 실패했다면 NULL..

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;
        
    //free block 헤더, 풋터, 에필로그 헤더 초기화.
    PUT(HDRP(bp), PACK(size,0)); // free 헤더
    PUT(FTRP(bp), PACK(size,0)); // free 풋터
    PUT(HDRP(NEXT_BLKP(bp)),PACK(0,1)); // 새로운 에필로그 헤더
    
    //앞선 블록이 free라면 연결해주겠다..
    return coalesce(bp);
    
    }

그래서 이 CHUNKSIZE/WSIZE로 들어가게 된다면
4096/4 = 1024가 들어가게되고
짝수라서 다시
1024 * 4 = 4096이
그대로 할당하는데,
....

에필로그 헤더를 아예 새로 갱신해주는데
이런 식이면 init때 에필로그 헤더를 만들어줄 필요 없는게 아닌가? 라는 의문...

아마 과정을 설명하는 포멀한 코드이기때문에 그렇지 않을까 추측...

아마 헤더 + 4096 + 풋터 + 에필헤더 라서
헤더(4) + 4096 + 풋터(4) + 에필헤더(4) ...뭐 그런
주솟값 계산 얘기...

mm_free

헤더로 가서 사이즈 찾음.
헤더 갱신!
풋터 갱신!

연결!
끝!

mm_malloc


// 아래 함수는 정렬만 하고 있을 뿐, find_fit, extend_heap의 경우까지 고려하지 않고 있음.
void *mm_malloc(size_t size)
{
    // 받은 size에 패딩 등 거친 크기 설정.
    int newsize = ALIGN(size + SIZE_T_SIZE);
    //newsize만큼 p를 늘림.
    void *p = mem_sbrk(newsize);
    if (p == (void *)-1)
	return NULL;
    else {
        // 블록 시작주소 p에 크기 정보 저장.
        //(size_t *) <== (void *)를 형변환.
        // * ((size_t *)p) <== 형변환한 포인터를 참조해서 값을 설정하겠다.
        // ...size로! 
        *(size_t *)p = size;
        // 크기 정보 다음의 실제 데이터 부분 시작 주소 반환.
        return (void *)((char *)p + SIZE_T_SIZE);
    }
}

SIZE_T_SIZE를 더하면,
newsize는 (내가 받을 크기 + 헤더풋터) < 정렬 시킴
으로 해서
그 크기만큼 brk를 늘려서
만약 잘 늘어났다면
size *p로 포인터 자료형을 형변환을 해서
값으로 size을 넣은 뒤
...
실제 데이터 시작 부분 주소를
반환해야 하기때문에

나는 헤더만큼 움직인다 정도로 이해했는데
(보통 헤더 부분에 size넣고
실제 사용 가능 위치는 헤더 다음이니까)

근데 그럼 SIZE_T_SIZE가 헤더풋터로 이루어졌다는 말이랑 다름..

아마 이거 헤더만 넣어서 만든거 아닌가 싶어...

아무튼 왜 그런 이상한 위치로 가는가!

brk는 처음 extend_heap으로 저 멀리 가있을텐데 왜 ....!! 저걸 포인터로 쓰는가!

라는 거네.

아 알았다
그렇구나....

여기서 brk 설정을
현재 어디까지 쓰고 있는지를 표시하고있는거고
그래서 malloc을 brk 움직이는걸로 해서...

헤더로만 이루어졌나봄...

profile
개발자 희망...

0개의 댓글