OOM report의 free와 managed, mapped와 shmem을 모두 더하면 시스템의 memory 사용량이 나올까요? 이 값들은 합계표의 서로 겹치지 않는 칸이 아닙니다. 어떤 값은 allocation 상태, 어떤 값은 LRU 분류, 다른 값은 주소 범위나 page-table 비용을 나타냅니다.
여기서는 책의 진단 출력에 등장하는 항목을 무엇을 세는지에 따라 묶겠습니다. 원본 예제는 6.0.0+ build의 로그이며, 아래 counter 이름은 그 설명을 읽는 기준입니다. 출력 항목과 내부 counter는 kernel version에 따라 달라질 수 있습니다. Linux v6.12의 변경 경로를 덧붙인 곳은 별도로 표시했습니다.
먼저 page 수와 KiB를 맞춥니다
원본 예제의 첫 global 묶음은 base page 수를 표시합니다. 뒤의 node·zone 묶음에서는 kB 또는 KiB 상당의 크기로 표시되는 항목도 있습니다. 화면에 같은 active_anon이 나오더라도 앞 숫자에 바로 뒤 숫자를 더하시면 안 됩니다.
Base page가 4 KiB라면 100 pages는 400 KiB입니다. 그러나 이 배율은 표기가 page 수일 때만 적용합니다. 이미 KiB로 출력한 kernel_stack 등에 같은 배율을 다시 곱하면 네 배로 잘못 계산하게 됩니다.
Slab의 내부 counter 이름에 붙은 _B도 주의하셔야 합니다. Byte 기반 counter를 구현에서 page나 출력 단위로 바꾸는 경로가 있습니다. 내부 counter의 단위와 로그에 인쇄된 단위는 같은 질문이 아닙니다.
LRU 상태와 mapping 통계입니다
| 출력 이름 | 관련 counter | 관찰하는 대상 |
|---|---|---|
active_anon, inactive_anon |
NR_ACTIVE_ANON, NR_INACTIVE_ANON |
Swap-backed 쪽 active·inactive LRU입니다. Shmem을 포함할 수 있어 process의 RssAnon 합계와 같지 않습니다. |
active_file, inactive_file |
NR_ACTIVE_FILE, NR_INACTIVE_FILE |
일반 file 쪽 LRU입니다. Active/inactive를 반대로 해석하지 않습니다. |
isolated_anon, isolated_file |
NR_ISOLATED_ANON, NR_ISOLATED_FILE |
Migration·reclaim 등의 처리를 위해 LRU에서 분리한 pages입니다. |
unevictable |
NR_UNEVICTABLE |
현재 reclaim 대상에서 제외한 memory의 분류입니다. mlock() 외의 조건과 상태 전환도 구분합니다. |
mapped |
NR_FILE_MAPPED |
File-page mapping에 관한 통계입니다. 한 page를 여러 process가 mapping한 횟수의 합이 아닙니다. |
shmem |
NR_SHMEM |
Shmem pages의 통계입니다. File 형태로 접근한다는 사실과 LRU에서 anonymous 쪽 특성을 갖는다는 사실이 공존합니다. |
Isolated 값이 커졌다는 사실만으로 leak이라고 결론을 내릴 수는 없습니다. 정상적으로 분리해 처리하는 중인지, 어떤 작업의 진행이 늦어지는지를 함께 보아야 합니다. 반대로 active에 있다는 이유만으로 이후 reclaim에서 절대로 회수되지 않는 것도 아닙니다.
File 변경과 kernel metadata의 비용입니다
| 출력 이름 | 관련 counter 또는 경로 | 관찰하는 대상 |
|---|---|---|
dirty |
NR_FILE_DIRTY |
아직 저장할 변경분이 있는 file pages입니다. |
writeback |
NR_WRITEBACK |
Writeback 중인 pages입니다. 같은 page가 다시 dirty해질 수 있습니다. |
slab_reclaimable |
NR_SLAB_RECLAIMABLE_B |
Shrinker 등으로 회수를 시도할 수 있는 slab입니다. 전량 즉시 회수 가능한 양은 아닙니다. |
slab_unreclaimable |
NR_SLAB_UNRECLAIMABLE_B |
그런 방식으로 회수하지 않는 slab입니다. 크다는 사실만으로 driver 결함을 확정하지 않습니다. |
kernel_misc_reclaimable |
NR_KERNEL_MISC_RECLAIMABLE |
Slab 외 reclaimable kernel memory를 구분하는 counter입니다. 사용 여부도 해당 version에서 확인합니다. |
pagetables |
NR_PAGETABLE |
User mapping을 관리하는 page-table memory입니다. Data RSS와 다른 비용입니다. |
kernel_stack |
NR_KERNEL_STACK_KB |
Task의 kernel stack 비용입니다. 내부 이름도 KiB 단위를 드러냅니다. |
writeback_tmp |
NR_WRITEBACK_TEMP |
일반 dirty 또는 writeback 항목과 별도의 용도로 추적하는 임시 buffer입니다. |
bounce |
NR_BOUNCE 등 해당 version의 경로 |
장치가 접근할 수 있는 주소 범위를 맞추기 위한 buffer입니다. Architecture와 configuration의 영향을 받습니다. |
shmem_thp |
NR_SHMEM_THPS |
THP 형태로 allocation한 shmem memory입니다. |
shmem_pmdmapped |
NR_SHMEM_PMDMAPPED |
실제 PMD level로 mapping된 shmem 부분입니다. |
anon_thp |
NR_ANON_THPS |
Anonymous THP backing에 관한 통계입니다. 임의의 모든 large folio와 같은 범위라고 가정하지 않습니다. |
shmem_thp, anon_thp처럼 큰 backing을 세는 항목과 shmem_pmdmapped처럼 실제 PMD mapping을 세는 항목도 분리해야 합니다. 큰 folio를 보유해도 모든 mapping이 큰 leaf entry를 쓰는 것은 아닙니다.
Dirty와 writeback을 무조건 서로 겹치지 않는 두 집합으로 보아서도 안 됩니다. I/O 중 다시 수정된 page가 있기 때문입니다. 이 두 값이 큰 상황에서는 저장 장치의 처리량, filesystem 상태와 dirty throttling까지 함께 따라가야 합니다.
Zone의 크기와 allocator 여유입니다
| 이름 | 구분 |
|---|---|
spanned |
/proc/zoneinfo 등에서 확인하는 PFN 범위입니다. Hole을 포함할 수 있습니다. |
present |
실제 존재하는 pages입니다. |
managed |
Allocator가 관리하는 pages입니다. 이미 allocation된 pages도 포함합니다. |
min, low, high |
Watermark입니다. 세 숫자를 더해서 reserve를 구하지 않습니다. |
protection, lowmem_reserve[] |
낮은 zone을 다른 요청으로부터 보호하는 fallback 제약을 읽습니다. |
reserved_highatomic |
높은 order의 atomic allocation을 위해 보호하는 자원입니다. 출력 단위와 내부 page 수를 구별합니다. |
writepending |
Zone의 dirty/writeback 부담을 보는 항목입니다. Dirty 통계와 별개의 추가 RAM pool이 아닙니다. |
Zone의 active_anon, inactive_anon, active_file, inactive_file, unevictable |
해당 NR_ZONE_* counter로 읽는 zone별 상태이며 node 전체 값과 범위가 다릅니다. |
mlocked |
NR_MLOCK. Resident pages의 locking과 연결되며 unevictable 전환과 집계 시점이 다를 수 있습니다. |
free |
NR_FREE_PAGES를 출력합니다. 현재 요청이 모든 free pages를 사용할 수 있다는 뜻은 아닙니다. |
free_cma |
NR_FREE_CMA_PAGES. CMA에 대한 사용 제약이 남습니다. |
Node의 LRU 통계와 zone의 NR_ZONE_* 통계도 같은 범위를 두 번 적은 것이 아닙니다. Node는 여러 zone을 포함할 수 있습니다. 출력 묶음의 범위를 확인하지 않고 반복되는 이름끼리 더하면 의미가 없어집니다.
all_unreclaimable?는 reclaim이 진전을 만들지 못했던 상태를 해석하는 단서입니다. 영구적으로 한 page도 회수할 수 없다는 선언이 아닙니다. 재시도와 kswapd_failures 등의 조건을 해당 version에서 확인해야 합니다.
PCP와 buddy 사이의 이동도 통계에 반영됩니다
Linux v6.12에서 buddy의 block을 PCP로 가져오면 buddy 쪽 free counter는 줄고 PCP의 count는 늘어납니다. 반대로 PCP를 drain해 buddy에 돌려주면 그 반대 경로를 거칩니다. NR_FREE_PAGES와 free_pcp를 언제나 같은 pool의 전체와 부분으로 간주하지 않는 이유입니다.
local_pcp는 현재 CPU의 일부를 살펴보는 값이므로, 전체 CPU의 free_pcp와 별개의 추가량으로 다시 더하지 않습니다. 진단용으로 합한 총량과 allocation 시 watermark 판정에 사용하는 counter도 같은 식이 아닙니다. 동시 갱신 중인 여러 값을 모아 만든 총합은 정확한 순간 snapshot도 아닙니다.
이 경계는 account_freepages(), rmqueue_bulk(), __rmqueue_pcplist()와 free_unref_page_commit()의 갱신 지점을 함께 보면 확인하실 수 있습니다. Linux v6.12 mm/page_alloc.c
Buddy 표는 block 수에 크기를 곱합니다
아래는 실제 장비에서 얻은 로그가 아니라, 4 KiB base page로 만든 독립적인 계산 예시입니다.
| 분포 | Order 0·1·2·3의 free block 수 | 총량 |
|---|---|---|
| A | 8, 0, 0, 0 |
8 * 4 KiB = 32 KiB |
| B | 0, 0, 0, 1 |
1 * 32 KiB = 32 KiB |
두 경우의 총량은 같지만, A의 order-0 blocks가 서로 합칠 수 없는 위치에 흩어져 있다면 현재 order-3 block은 없습니다. B에는 정렬된 32 KiB block이 있습니다. 그래도 B에서 임의의 요청이 반드시 성공한다는 뜻은 아닙니다. Zone·GFP·watermark·reserve 조건이 남습니다.
Migrate type별 괄호 표시는 그 order에서 어떤 종류의 free block이 있는지 알려 줍니다. U/M/E/H/C/I는 각각 Unmovable, Movable, Reclaimable, HighAtomic, CMA, Isolate입니다. 표시가 있다고 모든 요청이 그 block을 사용할 수 있는 것은 아닙니다.
마지막 합계도 별도로 읽습니다
pages RAM은 물리 memory의 전체 범위에 관한 집계이며, 그 아래의 HighMem·MovableOnly·reserved·CMA 관련 값은 서로 다른 성격을 나타냅니다. reserved를 allocator의 totalreserve_pages와 같은 변수로 바꾸어 읽어서는 안 됩니다.
v6.12의 __show_mem()에서 RAM 합계는 각 populated zone의 present_pages, 이 reserved는 present_pages - zone_managed_pages(zone)를 합산합니다. 따라서 이 reserved는 이미 free pages 안에 별도로 묶어 둔 lowmem reserve가 아닙니다. pages HighMem/MovableOnly도 같은 출력의 zone 분류에 따른 별도 집계이며, 모든 64-bit 시스템에 일반적인 HIGHMEM 영역이 있다고 가정하지 않습니다.
Swap 관련 출력에서도 현재 swap cache pages, swap cache의 누적 add/delete/find event, free swap와 total swap을 구분합니다. Cache에 있는 pages는 현재 RAM의 객체이며, swap device의 빈 공간과 같은 양이 아닙니다. Event counter를 memory 크기에 더할 수도 없습니다.
결국 비교 순서는 단위, 범위, 상태, 요청 조건입니다. 같은 이름이 두 번 보인다고 같은 양인 것도 아니고, 같은 단위를 쓴다고 서로 더할 수 있는 것도 아닙니다.
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access edition, 2025, §14.4.3–14.5.1. 진단 출력의 필드 범위를 원본과 대조했습니다.
- Linux v6.12
mm/show_mem.c: node·zone·전역 출력은 실제 version의 구현과 함께 읽어야 합니다. - Linux v6.12
mm/page_alloc.c: PCP와 buddy의 이동 및 counter 갱신입니다.