오늘은 Heap에 대해서 이야기 해보려고 한다.
힙은 동적 메모리 할당으로 런타임에서 필요한 만큼 메모리를 할당하여 사용한다.
Stack에서는 Stack Frame을 이용해서 데이터들을 구조적으로 관리한다면 Heap에서는 Malloc Chunk를 이용하여 데이터를 구조적으로 관리한다고 보면 이해하기 편하다.
그렇다면 Malloc Chunk에 대해서 이야기 해보도록 하겠다.
Malloc Chunk는 동적으로 할당 할 때 메모리 블록의 내부 구조를 의미한다. 그래서 Stack frame을 예시로 든 것이다.
사진을 보면 Top chunk부터 여러 chunk들이 쭉 있는 것을 볼수가 있다. 그렇다면 이런 것을 Heap of main thread 또는 Main Arena로 부른 다는 것을 알수가 있는데 그럼 Main Arena가 무엇인지 main thread가 무엇이길래 같이 불리는지 알아보겠다.
우선 그전에 thread는 한 process가 있을 때 그 process 내에서 thread는 개인이 할 수 있는 task를 전담해서 수행하는 걸로 이해하면 편합니다. 예를들어 자동차 제조회사를 process라고 보면 자동차 휠을 전담해서 만드는 부서 또는 엔진을 전담해서 만드는 부서 이런 부서들을 thread로 보면 이해가 편합니다. 이런 부서들은 제조회사의 자원을 이용가능해야 일을 할 수 있으니 똑같이 컴퓨터에서도 그 process가 갖고 있는 자원을 이용할 수가 있다.
여기서 Arena는 멀티스레드 환경에서 사용되는 특별한 Heap 영역으로 이해하면 편하다. 왜 필요하냐면 동시성 문제를 해결하기 위해 설계 되었다. 여러 thread가 동시에 메모리를 사용하기 위해 할당하거나 해제하면 동시성 문제가 발생 할 수가 있기 때문이다. 이렇게 각 thread가 heap 영역을 할당을 받고 즉 arena를 할당 받고 각 스레드가 process의 영역의 자원을 할당 받는 동시성 문제를 해결할 수 있다. 좀 더 자세히 보충을 하자면 각 thread마다 heap 영역이 있는것이 아니라 멀티스레드 환경에서 arena를 공유하는 것이지만 각 스레드마다 고유한 arena를 사용하게 된다. arena는 하나만 있는 것이 아니라 cpu core에 따라서 갯수가 달라질 수 있다.
main arena는 그 process가 처음 메모리를 할당 받은 영역을 말한다.
이렇게 heap 영역에 있는 main arena를 이용해서 여러 thread가 자원을 가지고 task를 수행하게 된다. 그렇게 할당 받은 arena에서 데이터를 malloc chunk의 구조를 이용하여 관리하는 것이다.
이제는 더 구체적으로 chunk의 구조에 대해서 이야기 해보도록 하겠다.
이해를 돕기위해 사진을 첨부 하였다.
chunk에 존재하는 prev_size, size, flag, data가 무엇을 의미하는지 설명하려고 한다.
1. prev_size는 말 그대로 전에 있던 chunk의 크기를 적어 놓는 필드인 것이다.
2. size는 현재 이 chunk의 크기를 적는 필드이다.
3. Flag는 A, M, P로 3가지가 있다
NON_MAIN_ARENA(A) - 이 chunk가 main arena의 메모리 영역에서 메모리를 할당 받았는지의 유무를 알려주는 flag이다. 즉 main arena 이외의 arena에서 할당된 chunk의 경우 이 flag가 설정된다.
IS_MMAPPED(M) - mmap()을 통해서 메모리를 할당 받았는지의 유무를 알려주는 flag이다. 일반적으로 mmap()은 큰 메모리 영역을 할당 받을 때 사용된다.
PREV_INUSE(P) - 이 flag는 전에 있는 chunk가 free가 되었는지를 알려주는 flag이다. 그래서 이 flag에 값이 설정되어 있다면 전에 있는 chunk는 현재 사용 중이라는 것을 알 수가 있다.
이렇게 사용 중일 때는 이런 chunk structure을 이용하지만 free를 한다면 어떻게 바뀌는지 한번 알아보려고 한다.
좀 더 이해하기 편하게 사진을 첨부하였다.
사용할 때와 같은 필드도 있지만 달라지는 필드가 대부분이다. 그럼 각 필드에 대해서 알아보도록 하겠다.
fd (forward pointer): 이 포인터는 free된 chunk의 연결 리스트에서 다음 chunk를 가리킨다. chunk가 사용 중인 경우에는 이 필드는 사용자 데이터의 일부로 간주된다.
bk (backward pointer): 이 포인터는 free된 chunk의 연결 리스트에서 이전 chunk를 가리킨다. 마찬가지로, chunk가 사용 중인 경우에는 이 필드는 사용자 데이터의 일부로 간주된다.
fd_nextsize: 현재 chunk보다 큰 크기의 다음 chunk를 가리키는 포인터이다.
bk_nextsize: 현재 chunk보다 작은 크기의 이전 chunk를 가리키는 포인터이다.
그렇다면 이렇게 free된 chunk는 계속 arena에 있는 것이 아니라 bins에 들어가서 따로 관리된다. 그 이유는 free가 된다고 OS에 메모리를 돌려주지 않기 때문에 bins을 통해서 free된 메모리 영역을 따로 관리 하는 것이다. bins에도 여러 종류가 존재하는데 설명은 아래와 같다.
Fast Bins
Unsorted Bin
Small Bins
Large Bins
- 큰 크기의 메모리 블록을 관리하는 bins.
- 크기별로 구분되지만, small bins보다 크기 범위가 넓음.
이렇게 오늘은 heap 영역에서 데이터를 어떤 구조로 어떤 방법으로 관리하는지를 알아보았다.
더 자세한 설명은 아래 한 블로그를 추천하려고 한다. 자세한 설명이 있으니 한번 편하게 읽어보면 좋을 것 같다.