Kernel · Zephyr 3.7.0 LTS3 · source + diagram note

Memory Management

정적 스택, 고정 크기 slab, 가변 크기 heap은 서로 다른 메모리 경로다. Zephyr 3.7.0에서 각 경로의 크기·실패 조건·반환 책임을 나누어 보고, 사용자 모드의 메모리 도메인이 접근 권한을 어디서 제한하는지 확인한다.

기준 버전: Zephyr 3.7.0 LTS3 형식: 사용 예제 + 설명 + 그림 분석 방법: Linux 커널과 비교

전체 흐름

스택, slab, heap은 하나의 순서로 변환되는 단계가 아니다. 스택은 스레드 실행 공간, slab은 같은 크기의 블록 저장소, heap은 가변 크기 할당자다. 메모리 도메인은 별도의 접근 권한 경계이며 CONFIG_USERSPACE와 하드웨어 지원 여부에 따라 적용된다.

그림 1. Zephyr 메모리 선택 지도 — 독립 경로와 접근 경계
Zephyr 3.7.0 메모리 경로와 보호 경계 정적 스레드 스택, 고정 크기 메모리 슬랩, 가변 크기 힙은 서로 이어지는 단계가 아니라 목적이 다른 세 경로다. 사용자 모드 메모리 도메인은 할당과 별도의 선택적 접근 권한 경계다. 메모리 선택은 하나의 순차 파이프라인이 아니다 Zephyr v3.7.0 · 저장 공간·할당 방식·접근 권한을 각각 판단한다 정적 스레드스택 K_THREAD_STACK_DEFINE정의 시 저장 공간 확보 k_thread_createK_THREAD_STACK_SIZEOF 전달 스레드 실행 수명heap/slab alloc 없음 고정 blockslab K_MEM_SLAB_DEFINE같은 크기 N개 확보 alloc → 사용 → freeblock 반환 시 waiter 깨움 0 / −ENOMEM기다리다 만료: −EAGAIN 가변 크기heap K_HEAP_DEFINE경계 있는 관리 영역 alloc → 사용 → free가변 크기·동일 heap에 반환 pointer / NULLsystem heap은 별도 API 별도 축접근 보호 사용자 모드 + 지원 아키텍처일 때 memory domain / partition user thread에 특정 영역의 접근 권한을 부여 · 할당 성공이나 block 소유권을 뜻하지 않음 ISR에서 slab·k_heap 할당은 K_NO_WAIT만 사용한다. 기다리는 시간은 thread context에서만 의미가 있다.

스택·slab·heap은 별도의 선택지다. 메모리 도메인은 사용자 모드의 접근 경계로서 할당자와 독립적이다.

경로크기를 정할 때사용 중 실패반환 책임
thread stack스택 객체 선언 시설정에 따라 overflow 감지·치명 오류스레드 생명주기와 스택 객체 수명 관리
memory slab블록 크기와 개수 선언 시-ENOMEM 또는 대기 후 -EAGAINk_mem_slab_free()
k_heapheap 영역 선언 시할당 불가·timeout이면 NULL같은 heap에 k_heap_free()
memory domainpartition과 권한 정의 시허용되지 않은 사용자 접근은 fault할당·해제와 별도로 권한 범위 관리

소스 코드 위치

아래 설명은 Zephyr v3.7.0의 고정 커밋 36940db938a8f4a1e919496793ed439850a221c2와 해당 버전 문서에 맞췄다. kernel/mempool.c는 이 버전에서 k_malloc() 등 system heap 경로를 구현한다. 이를 slab이나 별도 고정 블록 풀과 동일시하지 않는다.

kernel/mem_slab.ckernel/kheap.ckernel/mempool.ckernel/mem_domain.cinclude/zephyr/kernel/thread_stack.h

사용 예제와 실제 계약

아래 세 코드는 API 사용을 설명하는 예제다. Zephyr 구현 코드의 발췌는 아니며, 실제 반환값과 호출 조건은 위의 v3.7.0 문서와 소스에서 확인할 수 있다.

1. 고정 크기 slab: 128바이트 × 16개

K_MEM_SLAB_DEFINE(packet_slab, 128, 16, 4);

void *block;
if (k_mem_slab_alloc(&packet_slab, &block, K_NO_WAIT) == 0) {
        process_block(block);
        k_mem_slab_free(&packet_slab, block);
}

K_MEM_SLAB_DEFINE(packet_slab, 128, 16, 4)는 128바이트 블록 16개를 마련하고 정렬값으로 4를 요청한다. v3.7.0의 매크로는 정렬과 블록 크기를 WB_UP()으로 머신 워드 단위에 맞춘다. 따라서 실제 정렬은 64비트 환경에서 8바이트로 커질 수 있다. 이 예제의 블록 저장 공간은 2,048바이트이며 slab 객체의 관리 필드는 별도다. K_NO_WAIT 할당이 실패하면 처리 함수를 호출하지 않는다. 정의 매크로

공식 API에서 빈 slab에 K_NO_WAIT로 요청하면 -ENOMEM, 기다리다 시간이 끝나면 -EAGAIN이다. ISR에서 호출할 때는 K_NO_WAIT만 사용한다. 성공한 블록은 같은 slab에 반환해야 한다.

2. 스택 객체: 요청 크기와 전달 크기를 구분

K_THREAD_STACK_DEFINE(parser_stack, 2048);

k_thread_create(&parser_thread, parser_stack,
                K_THREAD_STACK_SIZEOF(parser_stack),
                parser_entry, NULL, NULL, NULL,
                7, 0, K_NO_WAIT);

K_THREAD_STACK_DEFINE가 만든 객체는 단순 char[2048]가 아니다. 아키텍처·userspace 설정에 따른 정렬, guard, 예약 공간이 붙을 수 있으므로 k_thread_create()에는 sizeof(parser_stack) 대신 K_THREAD_STACK_SIZEOF(parser_stack)를 전달한다.

3. 별도 heap: 할당 실패와 timeout

K_HEAP_DEFINE(app_heap, 4096);

void *buf = k_heap_alloc(&app_heap, len, K_MSEC(5));
if (buf != NULL) {
        k_heap_free(&app_heap, buf);
}

K_HEAP_DEFINE(app_heap, 4096)는 system heap과 다른 이름 붙은 heap이다. 내부 관리 공간이 필요하므로 4,096바이트 전부가 사용자 payload가 되는 것은 아니다. 이 예제의 K_MSEC(5)는 메모리가 풀릴 때까지 스레드가 대기할 수 있다는 뜻이다. ISR 호출이라면 timeout은 K_NO_WAIT여야 한다.

소스 추적 순서

질문v3.7.0에서 볼 곳확인할 계약
slab이 비면?kernel/mem_slab.c · Memory Slabs APIfree list, wait queue, -ENOMEM/-EAGAIN, 반환 시 대기자 처리
heap이 비면?kernel/kheap.c · Memory Heaps APIsys_heap 할당, timeout 대기, NULL 반환
k_malloc는 어디로?kernel/mempool.cCONFIG_HEAP_MEM_POOL_SIZE 기반 system heap과 k_free
stack 실제 크기는?thread_stack.h · Threads 문서객체 길이, guard/reserved, K_THREAD_STACK_SIZEOF
user thread가 RAM을 볼 수 있나?Memory Protection Designdomain partition, user 권한, 하드웨어 제약

빌드 산출물은 객체가 실제로 포함됐는지 확인하는 보조 증거다. 할당 실패의 원인을 찾을 때는 devicetree status보다 해당 slab의 사용 블록 수와 heap의 가용량·요청 크기부터 본다.

내부 동작 분석

고정 블록 slab

빈 블록을 free list로 관리한다. 128바이트 요청과 1바이트 요청 모두 이 예제에서는 블록 하나를 점유한다. 크기 단편화 대신 고정 개수의 고갈이 설계 변수다. 모든 블록이 사용 중이면 스레드는 timeout 정책에 따라 대기할 수 있다.

가변 크기 heap

k_heap는 동기화와 대기 큐를 제공하고 내부 sys_heap를 사용한다. v3.7.0 문서는 해제된 인접 free chunk의 병합과 크기별 bucket을 설명한다. 따라서 단편화 위험을 말할 수는 있지만, 할당 루프가 무제한이라고 단정하면 안 된다. deadline에는 timeout 대기와 실제 보드 측정치를 함께 넣는다.

스택 객체

스택 객체의 총 메모리와 함수에 전달할 stack size는 같지 않을 수 있다. 사용자 모드 스레드용 스택은 권한 전환용 공간과 보호 정렬 조건이 붙을 수 있다. supervisor 전용이면 K_KERNEL_STACK_DEFINE가 더 적절한지 검토한다.

메모리 도메인과 가상 메모리

메모리 도메인은 사용자 스레드에 추가 partition 접근을 부여하는 장치다. supervisor 접근을 제한하는 수단으로 해석하지 않는다. MMU 기반 virtual memory와 demand paging은 지원·설정이 필요한 별도 기능이며 모든 Zephyr 보드의 기본 전제가 아니다.

불변 조건 확인 목록

대상지켜야 할 조건검증 방법
slab블록 크기 128은 정렬 4의 배수이고, 동시 점유는 16개 이하k_mem_slab_num_used_get()와 최대 사용량 측정
stack스택 객체는 스레드 실행 기간 동안 살아 있고, 생성 인자는 K_THREAD_STACK_SIZEOFstack usage 계측 및 생성 코드 확인
heap할당·해제는 같은 heap의 API 쌍을 사용하고 NULL을 처리소유권 추적·할당 실패 로깅
user partitionuser 스레드가 접근할 RAM은 domain 권한과 하드웨어 정렬 조건에 맞음대상 보드의 userspace 설정·fault 재현

slab 블록의 상태 전이

할당 요청의 성공·실패와 블록의 소유 상태를 구분한다. 다음 그림은 위 128×16 slab 예제에서 블록 하나가 할당되고 반환되는 과정을 보여준다.

그림 2. k_mem_slab_alloc/free의 블록 소유권과 빈 slab 분기
free list128바이트 블록이 반환되어 사용 가능
alloc 성공한 호출자가 블록 포인터를 소유
use다른 코드에 넘겼다면 반환 책임도 명확히 전달
free같은 slab에 반환. 대기자가 있으면 전달하고, 없으면 free list로
free list가 비었으면: K_NO_WAIT → -ENOMEM; 스레드가 기다렸다가 timeout이면 -EAGAIN. ISR은 기다리지 않는다.

대기자가 없다면 반환된 블록은 첫 칸으로 돌아간다. 대기자가 있으면 우선순위와 대기 순서에 따라 블록을 넘긴다. 실패 반환은 블록 상태가 아니라 해당 요청의 결과다.

실패 조건표

관찰뜻먼저 확인할 것
slab -ENOMEM기다리지 않고 블록을 얻지 못함동시 사용 블록 수, 반환 누락, burst 상한
slab -EAGAIN대기했지만 timeout반환자 실행 시간, 우선순위, timeout 설정
heap NULL요청 크기를 만족 못 하거나 timeout같은 heap의 가용량·요청 크기·해제 누락
스택 fault/overflow스택 경계 침범 가능실제 stack 사용량, guard 설정, call depth
user access fault허용되지 않은 RAM 접근 가능domain membership, partition 범위·속성

자주 발생하는 오해

스택 객체에 sizeof를 바로 전달

sizeof(parser_stack)는 guard·예약 영역 때문에 생성 API가 요구하는 크기와 다를 수 있다. K_THREAD_STACK_SIZEOF(parser_stack)를 사용한다.

슬랩 고갈을 시스템 초기화 실패로 해석

slab 객체가 준비되어 있어도 16개가 동시에 점유되면 새 요청은 실패하거나 기다린다. 실패는 사용량·반환 지연과 먼저 연결해 본다.

heap에서 기다리는 timeout을 ISR에 사용

ISR의 slab/heap 할당은 K_NO_WAIT여야 한다. 대기가 필요한 작업은 스레드 문맥으로 넘긴다.

system heap과 이름 붙은 heap을 혼동

k_malloc()/k_free()와 k_heap_alloc()/k_heap_free()는 경로가 다르다. 포인터의 소유 heap과 해제 API를 함께 기록한다.

메모리 도메인을 할당자로 취급

partition은 user 모드 접근 권한을 기술한다. 블록을 새로 확보하거나 supervisor 접근을 제한한다는 뜻이 아니다.

Linux와 비교할 때의 경계

Zephyr를 Linux의 페이지 캐시·프로세스 가상 주소 공간을 축소한 버전으로 보면 slab과 heap의 역할을 혼동하기 쉽다. 이 글의 slab은 고정 크기 블록 API, heap은 가변 크기 allocator, stack은 스레드 실행 공간이다. Zephyr에도 MMU 기반 virtual memory와 demand paging 문서가 있지만 해당 기능은 플랫폼·설정에 의존한다.

따라서 비교의 기준은 운영체제 이름보다 실제 빌드 설정과 하드웨어다. CONFIG_MMU, CONFIG_USERSPACE, heap 크기, stack 보호 설정이 무엇인지 먼저 확인한다.

메모리 요청의 시간 축

시점slab 예제heap 예제stack 예제
정의128×16 블록 저장소4,096바이트 요청 영역parser_stack 객체
커널 준비free list 초기화내부 sys_heap 초기화스레드 생성 전 객체 유효
사용블록 하나 할당·반환len만큼 할당·반환스레드 호출 프레임 저장
압력16개 점유 시 대기·실패공간 부족/timeout 시 NULL크기 초과 시 보호 방식에 따라 fault

이 표의 각 열은 별도 자원의 수명 주기다. 한 열이 다른 열로 순차 이동한다는 뜻은 아니다.

Bring-up 문제 사례

부하가 몰릴 때 slab만 실패한다

성공·실패 호출 수와 동시에 점유한 블록 수를 기록한다. 16개 상한에 닿았는지, 소비자가 k_mem_slab_free()를 늦게 하거나 잊었는지 확인한다. 무턱대고 timeout을 늘리면 요청 지연만 늘 수 있다.

heap은 남아 보이는데 큰 요청이 NULL이다

총 free bytes만으로는 해당 크기의 연속 chunk가 있다는 뜻이 아니다. 요청 크기 분포, 해제 패턴, 동일 heap 사용 여부와 timeout을 함께 기록한다.

사용자 스레드에서만 접근 fault가 난다

주소가 유효해도 사용자 모드에서 접근이 허용된 영역인지는 별도로 확인해야 한다. 스레드 자신의 스택은 사용할 수 있으며, 같은 도메인의 다른 사용자 스택은 아키텍처와 CONFIG_MEM_DOMAIN_ISOLATED_STACKS 설정에 따라 공유되거나 격리된다. partition으로 추가한 영역은 범위·접근 속성과 스레드의 도메인 소속을 확인한다. 모든 RAM 접근에 별도 partition이 필요하다는 뜻은 아니다. 사용자 스택 접근 규칙

추가 측정 항목

  • slab: k_mem_slab_num_used_get() 및 최대 사용량으로 16개 상한의 여유를 확인한다.
  • heap: 요청 크기 분포와 NULL 빈도, timeout 대기 시간을 같은 부하에서 측정한다.
  • stack: 실제 최대 사용량을 stack 분석 도구와 함께 확인하고 보호 설정별 총 객체 비용을 비교한다.
  • userspace: 같은 접근을 supervisor/user 모드에서 나누어 시험해 권한 오류와 메모리 부족을 분리한다.

코드 리뷰 확인 사항

  • 이 포인터는 stack·slab·이름 붙은 heap·system heap 중 어디서 왔는가? 누가 언제 돌려주는가?
  • 최악 부하에서 동시에 필요한 slab 블록 수와 stack depth는 얼마인가?
  • ISR에서 호출하는 slab/heap API의 timeout은 K_NO_WAIT인가?
  • k_mem_slab_alloc()의 오류 코드와 k_heap_alloc()의 NULL을 각각 처리하는가?
  • 사용자 스레드가 접근할 영역은 자신의 스택인가, 공유 스택인가, partition으로 추가한 영역인가? 해당 접근 권한과 하드웨어 제약을 확인했는가?

디버깅 및 계측 지점

먼저 보드·설정 값을 기록한다: CONFIG_HEAP_MEM_POOL_SIZE, CONFIG_USERSPACE, CONFIG_MMU, stack 보호/분석 설정. 다음에 할당 API별 요청 크기, 결과 코드 또는 NULL, 호출 문맥, 반환 지연을 기록한다. 보드별 차이는 이 값과 실제 RAM 배치가 다를 때 조사한다.

slab에 대해서는 사용 블록 수를 주기적으로 읽고, heap은 요청 크기와 실패율을 추적한다. 로그 자체가 timing을 바꿀 수 있으므로 계측을 켠 빌드와 제품 빌드를 구분한다.

실험 기록 양식

[실험 주제] Memory Management
[Zephyr 버전] Zephyr 3.7.0 LTS3
[보드/SoC]
[west manifest revision]
[관련 Kconfig]
[관련 devicetree node]
[관찰한 원본 코드]
[예상 상태 전이]
[실제 상태 전이]
[실패 시 return/log/assert]
[latency 또는 memory 측정값]
[다음 실험]

대상이 slab, heap, stack, memory domain 중 무엇인지 적고, 호출 문맥·요청 크기·실패 반환값·블록 반환 책임자를 함께 기록한다.

주요 용어

용어이 글에서의 정확한 뜻
k_mem_slab같은 크기의 블록 여러 개를 관리하는 커널 객체
k_heap한 영역에서 가변 크기 버퍼를 할당하는 동기화된 heap
sys_heapk_heap 아래의 저수준 allocator; 동시 호출 직렬화는 호출자 책임
system heapk_malloc()/k_free()가 쓰는 별도의 전역 heap 경로
stack object정렬·guard·예약 영역이 포함될 수 있는 스레드용 메모리 객체
memory domainuser thread의 RAM partition 접근 권한을 묶는 경계

다음 Zephyr RTOS 글