전공/운영체제
17. Free-Space Management
nongdamgom
2024. 1. 11. 23:11
Splitting
- 빈 공간에 대한 요청이 있을 때, 두 부분으로 쪼개서 요청만큼 return 하고 나머지 부분을 다시 free로 관리
- 이후 free list를 update 시켜준다
Coalescing
- 각 free chunk의 size가 요청한 크기보다 작아서, 연속되어 있는 작은 free chunk 들을 하나로 merge 후 return
- 서로 붙어 있는 free chunk 들만 large single free chunk로 만들 수 있다.
- free list가 address 순서대로 안되어있을수도 있음 => 정렬을 하든 해서 찾고 합치기
Tracking The Size of Allocated Regions
- free(void *ptr)은 포인터의 시작 주소만 받지, 크기는 모르는데 어떻게 전체를 해제할 수 있을까?
=> malloc을 할 때, 추가적인 바이트(여기서 8byte)를 더 자동으로 할당 시키고, 여기에 size 정보 저장
The Header of Allocated Memory Chunk
- 위에서 추가적으로 할당한 8byte를 구조체로 정의
/* a simple header */
typedef struct __header_t{
int size;
int magic;
} header_t;
- 이 header의 시작 부분을 hptr ==> dealloc 을 빨리 할 수 있게 함
- magic number 는 integrity checking을 위해 존재
- 이러한 header가 존재하기 때문에, user가 N byte를 요청하더라도 시스템에서는 N + size of header 만큼의 free 공간을 찾는다.
void free(void *ptr){
header_t *hptr = (header_t*) ptr - 1;
}
- 그냥 void* ptr을 header_t 형으로 변환해서 8 byte 만큼의 배열로 취급
- ptr - 1 => ptr에서 뒤로 8byte만큼 간 것과 동일 == header의 시작을 알게됨
- 이 hptr을 이용하여 size와 magic num의 정보를 얻을 수 있다.
- 전체 free 할 공간은 malloc으로 할당한 부분 + header 부분까지
Embedding A Free List
- malloc()과 같은 typical memory-allocator가 아직 구현되지 않은 상태라고 가정해보자.
- malloc, free를 쓰지 않고 free list(heap)을 관리 해야할 때, 어떻게 할 수 있을까?
typedef struct __node_t{
int size;
struct __node_t *next;
} node_t;
/* mmap() system call 사용
-> 원래 file을 메모리에 대응하기 위해 사용 했는데,
여기서는 OS로부터 페이지 단위로 메모리를 할당받기 위해서 사용 */
node_t *head = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
head->size = 4096 - sizeof(node_t);
head->next = NULL:
- malloc이 없기 때문에, node를 직접 heap 안에 수동으로 집어 넣음
- 실제 free하게 사용할 수 있는 부분은 header 8byte을 제외한 4088 size
Free Space With Chunks Allocated
- virtual address에서 heap의 시작주소가 16KB고, 4KB 만큼의 free chunk가 있다고 가정.
- 4KB(=4096)에서 100씩 세번 할당 => 남은 free chunk size를 구할 수 있어야 함
- 4096이지만 header 부분 제외 4088부터 108씩 세번 빼주면 3764를 구할 수 있다.
Free Space With free()
- 위 예시에서, 할당받은 세 부분 중 두번째 영역을 다시 free list로 돌려보내보자.
- free list에 추가는 head의 바로 뒤에 된다고 가정.
- head는 새롭게 추가된 free list의 주소를 가짐 => 16KB(시작주소) + 108(윗부분 할당 크기) = 16492
- free list의 첫번째 노드는, 다음 노드의 주소값을 가짐 => 16492 + 108 + 108 = 16708
Free Space With Freed Chunks
- 모든 공간을 free list로 다 돌려 보내면, 위와 같은 free list가 구성된다.
- free한 순서랑, free list의 순서랑 헷갈리지 않길 바람(지금 new node 추가 방식이 head 뒤라서 이렇게 됨)
- total free chunk size = 3764 + 100 + 100 + 100 = 4064
- 만약 이 상태에서 malloc(4000)을 하면, 각 free 노드의 사이즈가 충족되지 않아서 4064 > 4000 임에도 할당 불가
- => External Fragmentation
- 이를 해결하기 위해 Coalescing을 함.
- => 모두 합쳐지면 free list의 head는 16384를 가리키고, 그 사이즈는 4088이 돼서 할당 가능
- (4064 => 4088 은 header 공간 차이 , merge 했기 때문에 + 24byte가 된 것)
Growing The Heap
- 대부분의 heap은 시작할 때 매우 작은 사이즈를 할당 받음.
- 사용하다 부족하면 brk()나 sbrk() system call을 사용하여 OS에 추가 메모리 할당을 요청
- 이 때, 추가적으로 할당 받는 메모리는 physical mem에서 연속적인 공간에 위치할 필요는 없다.
Managing Free Space: Basic Strategies
- free space를 다루는 일반적인 방법들
- Best Fit : free list를 다 살펴보고, 요청받은 메모리를 할당해줄 수 있는 가장 작은 사이즈의 노드 선택
- Worst Fit : Best Fit과 비슷하나, 가장 큰 사이즈의 노드를 선택함
=> free list를 전부 탐색해야만 선택을 할 수 있다는 단점 존재, So?
- First Fit : 앞에서부터 탐색, 만족이 되는 사이즈의 노드가 나오면 바로 pick
- Next Fit : 앞에서부터 선택해서 pick 하는데, 다음번에 또 요청이 들어오면 그 pick한 다음 노드부터 탐색 시작
- => 계속 앞에 노드만 사용하면, 앞에 노드 사이즈는 점점 작아지고, 작은 사이즈의 요청만 계속 들어오면 또 앞에 노드에서만 할당을 하게 됨, 이후에 큰 사이즈의 요청이 들어올 때 그걸 만족할 노드를 찾으러 뒤로 많이 가야하는 First Fit의 단점을 보완 => free list의 노드들을 균형있게 사용할 수 있게 됨.
Other Approaches: Buddy Allocation
- allocator가 요청 받은 사이즈를 충분히 할당 할 수 있을만큼 반으로 계속 나눠서 할당하는 방식
- 다음에 또 7KB를 요청하면, 쪼갠 정보가 남아 있어서 그냥 8KB를 return 할 수 있게 됨
- 만약 dealloc을 했는데, 옆 공간이 할당 x 면, 자동으로 coalecsing 진행 돼서 합쳐진다.
- 만약 alloc(1025)를 하면, 만족하는 최소 byte가 2KB라서, 2048 - 1025, 즉 1023의 공간은 사용x인데 낭비가 됨
- => Internal Frgmentation
Other Approaches: Segregated List
- 어떤 패킷같은 것에 자주 요청되는 메모리 사이즈가 존재한다고 할 때, 매번 할당해주는 것이 아니라 한번에 크게 할당해 놓고, 그 메모리를 자주 요청되는 byte size로 나눈 배열로 관리(memory pool), 요청될 때마다 그 배열 원소를 하나씩 return 하는 방식으로 free list를 관리한다.
- 위의 buddy allocation의 단점도 해결 가능 => 애매한 byte의 사이즈를 미리 잘라놓으면, 낭비되는 공간 줄이기 가능
- 근데 이 memory pool의 크기를 얼마로 해야 할지에 대해서는 open question.
- => Slab allocator가 해결해주고 있다.
- slab allocator
- 리눅스 커널에서 사용, 각각의 object(구조체 변수 => lock 같은거)를 위한 memory pool(= object cache)을 만들어줌
- 각각의 object에서 할당 요청이 오면, 주 memory allocator로 보내지 않고 각 memory pool에서 해결
- 처음에는 memory pool을 작게 시작했다가, pool이 꽉 차면 더 달라고 요청하는 방식으로 해결.