커널 스택 16KB는 꼭 필요한가 — LPC 2026에서 논의된 커널 스택 축소 방안

64비트 Arm과 x86 리눅스에서는 스레드 하나마다 16KB의 커널 스택이 잡힙니다. 이 메모리는 스왑할 수 없습니다. 스레드 하나만 보면 작은 양이지만, 유휴 상태의 Android 기기에도 스레드가 5,000개쯤 있습니다. 수백만 스레드가 도는 데이터센터 전체로 보면 커널 스택만 페타바이트 단위입니다. 전 세계적인 메모리 칩 부족으로 메모리 사용량을 줄이려는 관심도 다시 커졌습니다.

윌 디컨(Will Deacon)은 2026 Linux Plumbers Conference(LPC)에서 이 16KB를 다시 따져 봤습니다. 발표 자리는 메모리 관리 마이크로컨퍼런스였습니다. 이 글에서는 16KB가 정해진 과정, 스택을 줄이면 무엇이 깨지는지, 논의된 해법을 정리합니다. 실제 시스템에서 커널 스택을 얼마나 쓰는지도 직접 측정해 봅니다.

근거가 불분명한 16KB

디컨이 이력을 추적한 결과, 16KB는 거의 우연히 정해진 값이었습니다.

커널변경근거
3.7 (2012)arm64 머지, 스택 8KB8KB를 고른 이유는 디컨도 모름
3.11 (2013.7)arm64 16KB로 증가커밋 메시지는 “SpecWeb 벤치마크 실행 중 실패” 한 줄. 메일링 리스트 논의는 찾을 수 없음
3.16x86도 16KBarm64가 이미 그렇게 했다는 점과 “일부 GCC 버전이 훨씬 큰 스택 프레임을 만든다”는 점
4.5 (2016)arm64 per-CPU 인터럽트 스택인터럽트 처리가 스레드 스택을 쓰지 않게 됨. 오버플로 문제 상당수가 이때 풀렸을 가능성
4.14가상 매핑 커널 스택(CONFIG_VMAP_STACK)가드 페이지로 오버플로 탐지. 대신 스택을 32KB 경계에 정렬해야 함

즉 16KB로 늘린 뒤에 인터럽트 스택 분리처럼 스택 압박을 줄이는 변경이 들어왔지만, 크기는 다시 검토되지 않았습니다.

내 시스템의 커널 스택 측정

Ubuntu 24.04(커널 6.8.0-147-generic, 6 vCPU) 테스트 머신에서 측정했습니다. 먼저 /proc/meminfo의 KernelStack과 스레드 수를 비교합니다.

grep KernelStack /proc/meminfo
ls -d /proc/[0-9]*/task/* | wc -l
KernelStack:        3644 kB
223

3,644KB를 16KB로 나누면 약 228개입니다. 스레드 223개에 /proc에 보이지 않는 CPU별 idle 태스크를 더한 수와 거의 같습니다. 스레드마다 16KB가 그대로 잡혀 있다는 뜻입니다.

그러면 실제로는 얼마나 쓸까요? ftrace의 stack tracer(CONFIG_STACK_TRACER)는 모든 함수 진입 시 스택 깊이를 기록해 최대값을 남깁니다. 파일 검색, fsync를 동반한 쓰기, 소켓 연결을 몇 차례 실행한 뒤 확인했습니다.

sudo sysctl -w kernel.stack_tracer_enabled=1
# 부하 실행: find, dd conv=fsync, TCP 연결
dd if=/dev/zero of=/tmp/stk bs=1M count=200 conv=fsync
sudo cat /sys/kernel/tracing/stack_max_size
sudo head -40 /sys/kernel/tracing/stack_trace
sudo sysctl -w kernel.stack_tracer_enabled=0
4616
        Depth    Size   Location    (46 entries)
        -----    ----   --------
  0)     4616      64   _raw_spin_lock_irqsave+0x5/0x20
  1)     4552     192   rmqueue_bulk+0x50/0x770
  2)     4360      96   __rmqueue_pcplist+0x21f/0x300
  3)     4264     216   rmqueue+0x819/0xf00
  4)     4048     144   get_page_from_freelist+0x1d8/0x650
  5)     3904     104   __alloc_pages+0x1e9/0x350
  6)     3800      80   allocate_slab+0xa8/0x370
  7)     3720       8   new_slab+0x38/0x60
  8)     3712     216   ___slab_alloc+0x435/0x8e0
  9)     3496      16   kmem_cache_alloc+0x308/0x350
 10)     3480      96   mempool_alloc_slab+0x15/0x20
 11)     3384     112   mempool_alloc+0x83/0x1c0
 12)     3272      16   sg_pool_alloc+0x41/0x60
 13)     3256      80   __sg_alloc_table+0x124/0x1a0
 14)     3176      48   sg_alloc_table_chained+0x48/0xc0
 15)     3128      80   scsi_alloc_sgtables+0x8c/0x3e0
 ...
 28)     2112     128   blk_mq_submit_bio+0x5f1/0x6b0
 ...
 34)     1736     168   ext4_do_writepages+0x58a/0x7a0
 35)     1568     160   ext4_writepages+0xb5/0x190
 36)     1408     104   do_writepages+0xd0/0x1f0
 37)     1304      48   filemap_fdatawrite_wbc+0x75/0xb0

가장 깊었던 경로는 ext4 writeback → 블록 계층 → SCSI → 페이지 할당이었고, 4,616바이트였습니다. 16KB의 30%도 안 됩니다. 다만 이 측정에는 메모리 압박이 없었습니다. 아래에서 보듯 스택을 가장 깊게 만드는 경로는 direct reclaim입니다.

8KB로 줄이면 무엇이 깨지나

디컨은 8KB 스택으로 커널을 빌드해 테스트 세트를 돌렸습니다.

스택 크기결과
16KB현재 기본값
12KBMostafa Saleh의 패치로 커널 커맨드라인에서 지정 가능
8KB테스트 거의 통과. 메모리 압박 테스트만 실패. 다른 크래시도 대부분 direct reclaim이 관련
4KB부팅 불가

direct reclaim은 메모리 할당이 실패할 때 할당을 요청한 태스크가 직접 메모리 회수를 수행하는 경로입니다. 이미 깊은 호출 스택 위에서 파일시스템 writeback 같은 무거운 경로가 또 쌓입니다. 아른트 베르크만(Arnd Bergmann)도 스택 오버플로 대부분이 direct reclaim 때문이라고 했습니다. 사용자 컨텍스트에서 메모리를 할당하다가 direct reclaim으로 들어가는 경우입니다. 그는 스택 사용량이 1.25KB를 넘으면 경고를 내도록 설정해 운영해 왔습니다. 경고가 난 경우도 대부분은 문제가 없었다고 합니다.

스택 사용량을 늘리는 다른 요인도 있습니다.

  • 컴파일러 — 커널은 GCC 4.9부터 들어온 -fconserve-stack을 씁니다. Clang에는 알려진 문제 영역이 몇 군데 있고 개선 작업이 진행 중입니다.
  • Rust — Rust 코드는 스택을 더 많이 쓰는 편이라 1.25KB 경고 기준을 쉽게 넘습니다. Rust 코드용 스택 사용량 검사기도 아직 없습니다.
  • 새니타이저 — KASAN 같은 새니타이저도 스택을 늘립니다. 페터 지일스트라(Peter Zijlstra)는 새니타이저를 쓰는 사람이 직접 감당하면 된다고 했습니다. 베르크만은 Android 팀의 생각이 다르다고 답했습니다.

논의된 해법

Android 벤더 중에는 이미 out-of-tree 동적 스택 구현을 쓰는 곳이 있습니다. 하지만 이런 구현은 GKI(Generic Kernel Image)의 벤더 훅으로 만들 수 없어 배포가 어렵습니다. 그래서 메인라인에서 해결하려는 동기가 평소보다 큽니다.

접근제안자평가
전용 예외 스택윌 디컨예외 처리가 스레드 스택을 덜 쓰게 함. 패치 시리즈 있음
커맨드라인으로 스택 크기 지정Mostafa Saleh지금 바로 12KB로 운영 가능
direct reclaim을 전용 워커 스레드로수렌 바그다사리안(Suren Baghdasaryan)성공하면 8KB 가능성
블록된 태스크의 스택 메모리 회수David StevensRFC 시리즈. 지일스트라는 freezer를 쓰면 안전하게 회수할 수 있다고 지적
동적 스택 할당파샤 타타신(Pasha Tatashin) 제안, 리누스 발레이(Linus Walleij) 재검토커널 어디서든 메모리 할당이 일어날 수 있어 디컨은 “무섭다”고 평가. 견고하게 만들 수 있을지 의문
kmalloc()이 reclaim을 워커에 넘김아른트 베르크만우선순위·CPU 친화도 문제. 바그다사리안은 스택 전환이 더 단순하다고 반박
실행 중 스레드를 새 스택으로 전환하는 범용 기능매슈 윌콕스(Matthew Wilcox)XFS가 스택 때문에 워커로 일을 넘기면 lockdep이 혼란스러워함. 지일스트라는 x86에서는 비교적 간단하다고 답함
slab 할당자로 스택 할당키릴 슈테모프(Kirill Shutemov)64KB 페이지 시스템에서 낭비가 크다는 문제 제기. 가드 페이지가 없어 기각. 카나리는 사후 탐지만 할 뿐 오버런을 막지 못함

정리

세션은 결론 없이 끝났습니다. 다만 스택 전환 메커니즘이 머지않아 메일링 리스트에 올라올 가능성이 높아 보입니다. 위 측정처럼 평상시 커널 스택은 16KB의 3분의 1도 쓰지 않습니다. 문제는 드물게 깊어지는 direct reclaim 경로이고, 스택이 넘치면 시스템 전체가 죽는다는 점입니다.

스레드가 많은 임베디드나 모바일 시스템을 다룬다면 Saleh의 커맨드라인 패치를 지켜보십시오. 크기를 줄여 운영하려면 먼저 메모리 압박 상태에서 stack tracer로 최대 깊이를 측정해야 합니다. 평상시 측정값만 보고 크기를 정하면 안 됩니다.

참고

답글 남기기