QUESTION
free()를 호출했는데 process RSS가 그대로인 이유는 무엇인가?
malloc/free는 libc allocator API이고 kernel은 개별 allocation 크기나 object type을 모른다. allocator가 brk로 확장한 heap과 mmap으로 얻은 arena/chunk를 나눠 작은 object를 관리한다.
free는 object를 allocator에 반환할 뿐 page를 즉시 munmap하거나 kernel에 돌려준다는 보장이 없다. 같은 arena의 살아 있는 allocation, bin cache, fragmentation 때문에 virtual range와 resident page가 남을 수 있다.
STRUCTURE
구조 그림
Thread cache / bin
- free chunk
- size class
- 빠른 재사용
Arena
- allocated chunk
- free hole
- top chunk
Virtual mappings
- brk heap
- large mmap
- allocator metadata
Resident pages
- faulted page
- Private_Dirty
- trim/madvise 반환
free는 user object를 allocator에 돌려준다. 같은 page에 살아 있는 chunk가 있으면 VMA와 RSS는 그대로 남을 수 있다.
CALL PATH
호출 흐름
malloc object graph, allocator chunk, VMA, physical page 네 층의 수명이 다르다. leak 분석과 RSS 분석에 같은 도구 하나만 쓰지 않는다.
함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.
SOURCE COORDINATES
Linux 6.18.37 LTS 소스 위치
glibc 함수에서 멈추지 않고 syscall 구현과 커널 객체가 만나는 파일까지 내려간다. 링크는 동일한 태그의 원본 파일을 가리킨다.
| 파일 | 함수·구조체 | 여기서 볼 것 |
|---|---|---|
| mm/mmap.c | do_brk_flags(), do_mmap() | allocator가 요청하는 virtual range 변경 |
| mm/memory.c | do_anonymous_page() | heap/mmap page의 demand allocation |
| mm/madvise.c | madvise_dontneed_single_vma() | allocator trim 시 resident page 반환 가능 경로 |
COMPLETE PROGRAM
실행 예제 원본
아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.
cc -std=c17 -Wall -Wextra -O2 allocator_rss.c -o allocator_rss01#define _GNU_SOURCE
02#include <malloc.h>
03#include <stdio.h>
04#include <stdlib.h>
05#include <string.h>
06#include <unistd.h>
07
08int main(void)
09{
10 size_t length = 128UL * 1024 * 1024;
11 unsigned char *buffer = malloc(length);
12 if (buffer == NULL)
13 return 1;
14 memset(buffer, 0xa5, length);
15 printf("allocated and touched; pid=%ld\n", (long)getpid());
16 getchar();
17
18 free(buffer);
19 puts("freed to allocator");
20 getchar();
21
22 int released = malloc_trim(0);
23 printf("malloc_trim=%d\n", released);
24 getchar();
25 return 0;
26}
CODE NOTES
코드 조각별 설명
malloc(length)요청은 virtual object allocation이다. glibc는 크기와 설정에 따라 arena top chunk 또는 별도 mmap을 선택한다.
memset(buffer각 page를 write fault-in해 overcommit 예약만 한 상태와 실제 RSS 증가를 구분한다.
free(buffer)pointer ownership은 allocator로 돌아가지만 page가 즉시 kernel에 반환되는지는 chunk 위치와 allocator 정책에 달렸다.
malloc_trim(0)glibc에 사용하지 않는 heap page를 kernel에 돌려보내도록 요청하는 비표준 확장이다. 반환 1도 모든 free page가 사라졌다는 뜻은 아니다.
getchar();각 단계에서 외부 shell이 /proc/PID/smaps_rollup과 pmap을 비교할 수 있도록 멈춘다.
DETAILS
세부 동작
large allocation과 small allocation 경로가 다르다
큰 allocation은 별도 mmap을 사용해 free 시 munmap하기 쉽고, 작은 allocation은 arena page 안에 섞여 하나가 살아 있으면 page 전체를 반환하기 어렵다. threshold는 구현·튜닝·실행 이력에 따라 달라진다.
크기 하나를 기준으로 고정 동작을 API 계약처럼 가정하지 않는다.
multithread arena는 contention과 RSS를 교환한다
allocator는 lock contention을 줄이려고 여러 arena와 thread cache를 둘 수 있다. peak 뒤 각 arena에 빈 chunk가 흩어지면 process 전체 RSS가 높게 남는다.
MALLOC_ARENA_MAX 같은 tunable은 workload latency와 fragmentation을 측정한 뒤 적용한다.
leak과 retention을 구분한다
leak은 도달할 수 없는 object를 application이 free하지 않은 문제이고, retention은 allocator가 free page를 재사용하려고 보유하는 상태다. 둘 다 RSS가 높지만 해결 방법은 다르다.
ASan/LSan, heap profiler, malloc_info, smaps를 함께 사용한다.
OBJECTS
객체와 수명
| 대상 | 언제 생기고 없어지는가 | 확인할 값 |
|---|---|---|
user allocation | malloc에서 caller에게 넘어가고 free에서 allocator 소유로 돌아간다 | requested/usable size, owner |
allocator chunk/arena | process 수명 동안 재사용되며 trim/unmap에서 일부 반환된다 | bin, top chunk, fragmentation |
anonymous VMA/page | brk/mmap과 fault에서 생기고 madvise/munmap/reclaim에서 줄어든다 | Rss, Private_Dirty |
FAILURE PATH
실패 조건과 오해하기 쉬운 부분
| 겉으로 보이는 현상 | 실제 원인 후보 | 확인 방법 |
|---|---|---|
| free 뒤 RSS 유지 | arena retention/fragmentation | malloc_info와 smaps 비교 |
| malloc NULL/abort | ENOMEM 또는 heap corruption | errno, sanitizer, core dump |
| latency spike | arena lock, page fault, mmap/munmap | perf lock/fault와 allocator profiler |
LAB
직접 확인
- 세 getchar 단계마다 smaps_rollup Rss/Private_Dirty와 strace brk/mmap/munmap을 기록한다.
- 128 MiB 한 개 대신 4 KiB object 여러 개를 섞어 할당·부분 free해 fragmentation 차이를 본다.
- MALLOC_ARENA_MAX를 바꿔 multithread allocation benchmark의 peak RSS와 throughput을 함께 측정한다.
./allocator_rssstrace -e trace=brk,mmap,madvise,munmap ./allocator_rssPRIMARY REFERENCES