임베디드 면접 용어 사전
디버깅·신뢰성

스택 워터마크

스택을 특정 패턴으로 채워 두고 남은 흔적으로 최대 사용량을 재는 기법.

스택이 지금까지 최대 어디까지 사용됐는지 측정하는 기법 (stack high-water mark).

원리 — 패턴을 채우고 남은 것을 센다

① 스택 영역 전체를 특정 패턴으로 채운다 (예: 0xA5A5A5A5)

  [0xA5][0xA5][0xA5][0xA5][0xA5][0xA5][0xA5][0xA5]
  ↑ 스택 바닥                              스택 꼭대기 ↑

② 프로그램을 충분히 실행한다

  [0xA5][0xA5][0xA5][데이터][데이터][데이터][데이터][데이터]
              ↑ 여기까지 사용됐다

③ 바닥부터 세어 패턴이 그대로 남아 있는 길이 = 한 번도 안 쓴 여유

"현재 사용량"이 아니라 "역대 최대 사용량" 을 알려 준다는 것이 핵심이다. 스택은 함수가 반환하면 값이 남아 있어도 논리적으로는 비므로, 현재 SP만 보면 최악을 알 수 없다.

FreeRTOS에서

UBaseType_t remaining = uxTaskGetStackHighWaterMark(task_handle);
// 반환값 단위: 워드 (4바이트). 남은 여유 공간

if (remaining < 50) {
    log_warn("태스크 스택 부족: %lu 워드", remaining);
}
// 모든 태스크를 주기적으로 점검
void monitor_task(void *p) {
    for (;;) {
        for (int i = 0; i < TASK_COUNT; i++) {
            UBaseType_t hw = uxTaskGetStackHighWaterMark(tasks[i]);
            if (hw < THRESHOLD) report(i, hw);
        }
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

양산 펌웨어에도 남겨 두고 원격으로 수집하면 현장에서의 실제 사용량을 알 수 있다.

베어메탈에서 직접 구현

extern uint32_t _sstack, _estack;
#define PATTERN 0xA5A5A5A5

// 부팅 직후 (main 진입 전이 이상적)
void stack_paint(void) {
    uint32_t *p = &_sstack;
    uint32_t *sp = (uint32_t *)__get_MSP();
    while (p < sp - 16) *p++ = PATTERN;    // 현재 SP 아래까지만
}

uint32_t stack_unused(void) {
    uint32_t *p = &_sstack;
    while (*p == PATTERN && p < &_estack) p++;
    return (uint32_t)(p - &_sstack) * 4;   // 바이트
}

주의 — 칠하는 시점에 이미 스택을 쓰고 있으므로 현재 SP 아래만 칠해야 한다. 자기 자신의 스택 프레임을 덮으면 죽는다.

왜 필요한가 — 스택 크기 산정의 유일한 실증 방법

  • 정적 분석(-fstack-usage) — : 이론적 상한. 함수 포인터·재귀가 있으면 부정확
  • 실측(워터마크) — : 실제로 일어난 최악. 테스트하지 않은 경로는 못 잡는다

둘 다 필요하다. 정적 분석으로 상한을 구하고, 워터마크로 실제를 확인한 뒤 여유를 더한다.

산정 절차

  1. 넉넉하게 시작 (2KB) 2. 모든 기능을 실행하는 테스트를 오래 돌린다
  • ★ 정상 경로뿐 아니라 에러 처리 경로까지 반드시 태운다
  1. 워터마크 확인 → 실제 최대 사용량 파악
  2. 실제 사용량 + 30~50% 여유로 확정
  3. 양산 후에도 주기적으로 로깅

★ 2번이 중요하다. 평소에는 안 넘치다가 드물게 실행되는 에러 처리에서 넘치는 경우가 많다. printf("%f") 같은 것이 에러 경로에만 있으면 놓치기 쉽다.

놓칠 수 있는 것 — 인터럽트 중첩

  • 워터마크는 그 태스크가 실행 중일 때 발생한 인터럽트만 반영한다
  • 최악의 중첩 조합이 테스트 중에 발생하지 않았다면 잡히지 않는다

인터럽트 중첩 깊이 × 32바이트(FPU면 104바이트) 를 계산으로 더해 두는 것이 안전하다.

MPU 가드와 함께 쓴다

워터마크 : 얼마나 여유가 있는지 알려 준다 (예방) MPU 가드 : 넘치는 순간 잡는다 (검출)

둘은 역할이 다르다. 워터마크는 사후 통계이므로 실제로 넘치는 순간을 놓칠 수 있다. MPU 가드가 확정적 검출을 담당한다.

힙에도 같은 개념

xPortGetMinimumEverFreeHeapSize();   // 역대 최저 여유 힙

"역대 최저"를 보는 것이 핵심이라는 점이 동일하다. 현재 여유가 넉넉해도 피크에서 바닥을 쳤다면 위험하다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 디버깅·신뢰성 — JTAG/SWD·HardFault(fault 레지스터)·트레이스·watchdog·EMC