가상메모리 영역을 mmap이나 munmap 함수를 사용해서 생성하고 삭제할 수 있지만 동적 메모리 할당기를 사용하는 것이 좀 더 편리하고 호환성이 좋다고 생각하는 경우가 많다.
동적 메모리 할당기는 힙(heap)이라고 하는 프로세스의 가상메모리 영역을 관리한다. 보통 힙은 미초기화된 데이터 영역(BSS, Block Started by Symbol) 직후에 시작해서 위쪽으로(높은 주소 방향으로) 커지는 영역이다.
커널은 각 프로세스에 대해서 힙의 꼭대기를 가리키는 변수 brk(break)를 사용한다.
할당기는 힙을 다양한 크기의 블록들의 집합으로 관리한다.(여기에서의 블록은 allocator에서 관리하는 메모리 단위를 말한다.)
malloc과 free가 대표적이다.인텔은 4바이트 객체를 더블 워드라고 표현한다. 책에서는 일반적인 용어와 일관성을 갖기 위해 워드는 4바이트 객체로, 더블 워드는 8바이트 객체라고 가정한다.
malloc 함수는 32비트 모드에서는 주소가 8의 배수인 블록을 리턴하고 64비트 모드에서는 주소가 16의 배수인 블록을 리턴한다. 즉, 64비트 모드에서 주소는 항상 16의 배수이다.
예외적인 상황을 만날 경우 널을 리턴하고 errno를 설정한다. 기본적으로 malloc은 반환하는 메모리 주소 안의 값을 초기화하지 않는다. 초기화 해주기를 원한다면 calloc을 사용해야 한다. 이외에도 할당된 블록 수정을 위한 realloc도 있다.
malloc 같은 동적 메모리 할당기는 mmap과 munmap 함수를 이용해서 명시적으로 힙 메모리를 할당하거나 반환한다. 혹은 sbrk 함수를 이용할 수 있다.
sbrk는 성공 시 이전 brk 값을 반환하고 아니면 -1을 리턴하고 errno를 ENOMEM으로 설정한다. sbrk에 음수 incr를 인자로 주는 것은 가능하기는 하지만 복잡해진다. 반환 시에 나오는 주소가 이전 주소이기 때문에 이미 축소되어 쓸 수 없는 주소를 가리키고 있을 것이기 때문이다. 따로 반환값에서 빼고자 했던 주소값만큼을 응용 측에서 처리해야 한다.free는 블록의 시작을 가리키는 포인터를 인자로 주면 함수 내뷍서 반환 처리를 한다.프로그램을 실행시키기 전에는 자료 구조의 크기를 알 수 없는 경우들이 있기 때문이다. 정적으로 최대 배열 길이를 정해둘 수도 있겠지만 고정된 배열 크기가 있다는 것 자체가 큰 규모의 소프트웨어 제품에서는 유지 보수의 어려움이 있다.
중간 내용은 학습 키워드를 정리하면서 나왔던 내용이라서 따로 정리하지는 않았다.
이제 implicit free list 방식을 사용하는 간단한 할당기의 구현을 따라가 본다. 최대 블록 크기가 = 4GB라고 가정한다. 코드는 32비트 또는 64비트이고 프로세스에서 수정 없이 돌 수 있는 것은 64비트이다.
기본적인 malloc 대신 memlib.c라는 패키지에서 제공되는 메모리 시스템 모델을 사용한다.
mem_init: 힙에 가용한 가상메모리를 큰 더블 워드로 정렬된 바이트 배열로 모델링한 것이다.mem_heap과 mem_brk 사이의 바이트들은 할당된 가상메모리들이다.mem_brk 다음에 오는 바이트들은 미할당 가상메모리를 나타낸다.mem_sbrk: 추가적인 힙 메모리를 요청할 때 사용한다.PACK(size, alloc): header/footer에 저장될 크기 및 할당 정보가 담긴 형태의 바이트 데이터를 반환한다. (크기와 할당 비트를 통합해서 헤더와 풋터에 저장할 수 있는 값을 리턴)GET(p): 인자 p가 참조하는 워드를 읽어서 리턴한다.PUT(p, val): 인자 p가 가리키는 워드에 val을 저장한다.GET과 PUT에서 포인터를 unsigned int *로 형변환 하는 이유: 4bytes 단위로 저장되어 있는 header와 footer의 정보를 부호 정보 없이 그대로 읽고 싶기 때문.GET_SIZE: header 또는 footer에 있는 하위 3비트를 제외한 size 정보를 반환한다.GET_ALLOC: header 또는 footer에 있는 할당 여부 비트를 반환한다.HDRP: payload 시작 주소에서 4바이트 전으로 이동하여 header 영역에 접근한다.(포인터에서 WSIZE 바이트만큼을 뺀 주소로 접근하여 포인터를 반환한다.)FTRP: 현재 블록의 footer에 접근한다.(payload 시작 주소에서 header의 크기를 읽어서 현재 주소에서 더한다. 그 위치는 다음 블록의 payload이므로, 현재 블록의 footer에 접근하기 위해서 DSIZE(8바이트)를 빼준다.)NEXT_BLKP: 다음 블록의 payload 시작 주소를 반환한다.(현재 블록 헤더에서 크기를 가져와서 현재 payload 시작 주소에 더해준다.)PREV_BLKP: 이전 블록의 payload 시작 주소를 반환한다.(현재 payload 시작 위치에서 이전 블록의 footer를 통해 블록 size를 파악하여 빼준다.)