...
#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가 가용 블록 크기만 말하는건지, 헤더와 풋터를 포함하는 건지 잠깐 토론이 있었음.
새로 힙 만들어줌.
힙에 있는 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) ...뭐 그런
주솟값 계산 얘기...
헤더로 가서 사이즈 찾음.
헤더 갱신!
풋터 갱신!
연결!
끝!
// 아래 함수는 정렬만 하고 있을 뿐, 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 움직이는걸로 해서...
헤더로만 이루어졌나봄...