Heap이란?
-> 프로그램이 실행되는 도중 동적으로 할당되고 헤제하여 사용하는 메모리 영역
동적 메모리 할당자의 종류
- tcmalloc - google
- ptmalloc2 - glibc
- Libumem - Solaris
- Jemalloc - FreeBSD and Firefox
- dlmalloc - General purpose allocator
dlmalloc
현재 우분투에서 사용되는 메모리할당자의 기본 알고리즘은 dlmalloc과 동일
heap이 생기고 없어지는 원리
char *p = malloc(size)
원하는 size만큼 malloc을 호출하여 동적 메모리 할당
free(p)
사용한 메모리를 free하여 반환
생기는 위치
특정 크기의 메모리 영역을 미리 할당한 뒤 이 영역을 사용하는 방식 -> free한 뒤에도 이 부분은 남아있음
CHUNK

- mem : malloc으로 할당받은 부분
- chunk : header(사이즈 정보)와 mem을 포함하는 영역
- p = malloc(40); -> 0x2010의 주소를 return 받았다면
- strcpy(p,"AAAA");
- free(p);
- size : chunk의 크기
- 32bit는 8byte, 64bit는 16byte단위로 정렬
- prev_size : 인접한 앞쪽 chunk의 크기
- 앞쪽 청크가 free 될 때 prev_size가 초기화 됨
chunk size
size : chunk의 크기뿐만 아닌 3가지 속성도 나타냄
- 32bit는 8byte, 64bit는 16byte단위로 정렬
이 부분이 청크의 특성을 나타내는 데 사용
이 때 chunk사이즈 하위 3비트가 0이 된다.
왜냐면
1. 8바이트 정렬
64bit 시스템에서 메모리 블록은 최소 8바이트 경계에 맞춰 할당된다.
따라서 실제 크기는 8의 배수여야 하고, 그 결과 하위 3비트가 0이 된다.
2. 플래그 비트
하위 3비트는 크기정보 대신 malloc의 내부 플래그를 저장하는데 사용된다.
예를들어 01000 = 8
이 있을 때
1번째 0이 1이되면 즉 01100 이 되면 non_main_arena에 속하면 1, 아니면 0
2번째 0이 1이되면 즉 01010 이 되면 is_mmaped 로 할당되었다면 1, 아니면 0
3번째 0이 1이되면 즉 01001 이 되면 prev_inuse bit 즉 인접한 이전 청크가 할당되면 1, 해제되면 0이 된다.
non_main_arena
청크가 메인아레나가 아닌 다른 전용 아레나에서 할당되었는지를 표시
- 아레나(Arena) 란?
glibc malloc은 멀티스레드 환경에서 락 경합을 줄이기 위해 “아레나”라는 독립된 메모리 풀 단위로 관리한다.- main arena는 brk()/sbrk() 기반 힙과 mmap() 영역을 모두 관리한다.
- thread (non-main) arena는 mmap()으로 커널에서 한 번에 큰 블록(기본 64MiB)을 확보한 뒤 내부에서 잘라 쓰는 방식으로 운영된다.
- NON_MAIN_ARENA 플래그
- size 헤더의 LSB 비트 2(값 4)가 NON_MAIN_ARENA 플래그다.
- 1 → 스레드 전용 아레나에서 할당된 청크이다.
- 0 → main arena에서 할당된 청크이다.
- size 헤더의 LSB 비트 2(값 4)가 NON_MAIN_ARENA 플래그다.
- free() 시 동작
- free(p) 호출 시 size 하위 3비트를 검사해 아레나 소속을 판별한다.
- NON_MAIN_ARENA=1인 경우 해당 스레드 아레나의 free list로 반환한다.
- NON_MAIN_ARENA=0인 경우 main arena의 free list로 반환한다.
- 이로 인해 각 아레나가 독립적으로 자유 청크를 관리하여, 멀티스레드 환경에서 락 경합 없이 병렬 해제가 가능하다.
- free(p) 호출 시 size 하위 3비트를 검사해 아레나 소속을 판별한다.
is_mmaped
해당 청크가 mmap()으로 할당된 큰 블록인지 표시한다.
즉 1이면 mmap영역에서 왔으므로 free시 내부 free list로 가는게 아닌 munmap()로 바로 반환된다.
mmap 영역이란?
운영체제의 가상 메모리 공간 중, 파일이나 장치가 아닌 “익명(anonymous)” 메모리를 페이지 단위로 매핑(mapping)해 주는 구역을 말함.
- 일반적인 malloc()/free()가 이용하는 힙(heap)은 프로세스의 브레이크(brk) 포인터 (=> 현재 힙의 끝 이자 다음에 할당될 힙 공간의 시작점)를 위아래로 움직여 크기를 조절하는 반면,
- mmap()은 원하는 주소에 원하는 크기(보통 페이지(4KiB) 단위)로 직접 가상 메모리를 할당해 준다.
- munmap()이란?
mmap()으로 매핑한 메모리 영역을 운영체제에 “언매핑(unmapping)”하여 되돌려 주는 시스템 호출, 호출하면, p부터 length 바이트만큼 매핑된 페이지가 해제되어 다시 OS 관리로 돌아간다.
void *p = mmap(NULL, length, PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
// … 메모리 사용 …
munmap(p, length);
- mmap() 할당과 malloc() 할당의 차이
- 할당 방식
- malloc()
- 내부적으로 brk()/sbrk()를 호출해 힙 영역을 늘리거나 줄이며, 메타데이터와 자유청크(free chunks)를 한 공간에서 관리
- 작은 객체 여러 개를 효율적으로 처리하도록 설계됨
- mmap()
- 운영체제에 바로 시스템 콜을 보내 페이지 단위로 메모리를 새롭게 매핑(할당)
- 크기가 크거나(기본 임계값 넘는 경우) 특별 옵션이 필요할 때 사용
- malloc()
- 해제 방식
- free() → 내부 free list에 반환 → 이후 재사용
- munmap() → 운영체제에 바로 반환 → 프로세스 주소 공간에서 사라짐
- 단위 및 오버헤드
- malloc()은 바이트 단위(실제로는 8바이트 정렬) 관리, 메타데이터 오버헤드 적음
- mmap()은 페이지 단위(4KiB 이상) 관리, 시스템 콜 오버헤드가 크고 메모리 낭비(page rounding)가 발생할 수 있음
- 단편화(fragmentation)
- 힙 내부 단편화(internal fragmentation) vs 외부 단편화(external fragmentation)
- 매우 큰 할당/해제가 잦으면 힙 단편화가 심해질 수 있어, 대신 mmap()을 써서 큰 청크를 별도 관리
- 할당 방식
prev inuse bit

이렇게 이전 청크가 할당되면 001 아니면 000이다.
chunk의 종류
크게 3가지가 있다.
1. Allocated chunk
2. Freed chunk
3. Top chunk
Allocated chunk
malloc()을 호출했을 때 heap영역에 생기는 chunk

Freed chunk
해제된 청크
free()를 했을 때 실제로 반환되는 것이 아닌 힙에 남아있으며, allocated chunk구조에서 free chunk로 변경됨(data가 있던 부분에 fd, bk가 생김)
fd : 뒤에 있는 청크
bk : 앞에 있는 포인터

Top chunk
힙에 가장 마지막에 위치
새롭게 할당되면 top chunk에서 분리해서 반환하고, 인접한 청크가 free되면 병합한다.

새로운 malloc이 요청되고 재사용가능한 chunk가 없으면 topchunk에서 분리해서 반환한다.
dlmalloc의 알고리즘
- chunk의 크기 정보가 chunk의 앞 뒤에 저장 : boundary tag
- 재사용가능한 chunk를 크기단위로 관리 : binning
boundary tag
chunk가 할당될 때 크기정보가 해당 chunk의 size에 저장
이전 청크가 해제 free() 될 때 이전 청크의 크기 정보가 prev_size에 저장
allocated chunk와 freed chunk가 있을 때 인접한 앞/뒤 청크의 주소 계산이 가능하다.

이렇게 prev_size가 초기화 되어있다면 해당 청크의 기준에서 preb_size를 빼면 이전 청크의 주소가 나오게 된다.
그리고 뒤의 청크주소도 계산이 가능하다 해당 청크의 기준에서 size를 더하면 다음 청크가 나오게 된다.
이는 prev_size를 조작하면 이전 chunk를 조작할 수 있다.
그리고 size도 조작하면 다음 chunk의 위치를 바꿀 수 다.
binning
fast bin
small bin
large bin
unsorted bin
fast bin
size가 다른 10개의 청크를 각각 single linked list로 관리(FILO) -> 먼저들어오면 가장 마지막에 나감
해제가 되어도 다음 chunk의 prev_inuse 값이 변경되지 않음 -> 인접한 청크와 병합 X
small bin
size가 다른 63개의 청크를 double ilinked list로 관리(FIFO) -> 먼저들어오면 먼저 나감
size가 512byte 이하
해제되면 뒤 청크의 prev_inuse 가 해제되면 인접한 청크와 병함
large bin
특정 범위의 size가 다른 63개의 청크를 double linked list(FIFO)로 관리됨 -> 먼저들어오면 먼저 나감
크기가 512byte이상 되는 청크, 범위 내에서 size가 큰 청크가 제일 앞이다.
해제되면 뒤 청크의 prev_inuse 가 해제되고 인접한 freed chunk와 통합된다.
unsorted bin
unsorted bin에 들어가는 경우
free 했을 때 bin으로 가기 전 (fastbin 제외)
fastbin들이 병합(malloc_consolidate)되어 합쳐지면
bestfit으로 할당된 chunk의 남은 부분 remainder
인접한 청크도 이미 free되어있어 병합된 free 청크
unsorted bin에 나오는 경우
사용자가 malloc을 호출하여 요청한 size와 동일한 청크가 있다
즉 사용자가 요청하고 남은 부분이 Unsorted bin으로 들어간다.
병합
해제하려는 청크의 앞, 뒤 청크가 이미 해제되어 있을 때
1. 해제하려는 청크의 앞이 이미 해제되어 있을 때

A는 이미 해제되어 있고 B는 할당되어 있다. 이 때 B를 해제하면 이 때 prev_inuse 가 설정되어 있지 않기 때문에 이전 청크가 free되었다 판단한다. 이러면 prev_size가 초기화 되어있으므로, 해당 청크의 주소에서 prev_size를 빼면 이전 청크의 주소를 알 수 있고 이를 병합한다.
2. 해제하려는 청크의 뒤가 이미 해제되어 있을 때

B가 해제되어있고 A는 할당되어 있다. 이러면 size를 더해서 이전청크로 가고, 이전 청크 즉 B가 해제되어있는지 알기 위해 C까지 간다 즉 size를 한번 더 더한다.
이러면 prec_innuse를 확인하여 B가 free되어있는지 확인하고 A와 B를 병합한다.
이 때 병합되는 부분에 freed chunk의 포인터 fd와 bk를 정리해줘야 한다.
이를 unlink라 한다.
unlink
bin리스트에 등록된 청크를 제거하기 위해 포인터 fd, bk를 정리하는 작업
즉 노드 (bin에 속한 각 청크) 3개가 있고, 모든 fd, bk가 연결되어 있을 때
그 중 2번째 노드가 다른 청크랑 병합될 때 fd,와 bk를 다시 계산하는 것이다.
'pwanble' 카테고리의 다른 글
| 06. [How2Heap] - unsafe_unlink.c (0) | 2025.09.09 |
|---|---|
| 05. [How2Heap] fastbin_dup_consolidate.c (0) | 2025.09.05 |
| 04. [how2heap] - fastbin_dup_into_stack.c (1) | 2025.05.09 |
| 03. [How2Heap] - fastbin_dup.c (0) | 2025.05.09 |
| -02. calc_tcache_idx.c (1) | 2025.04.30 |