기록

[GPT가 알려주는] Watchdog 본문

잡동사니/GPT가 알려주는

[GPT가 알려주는] Watchdog

heylo 2025. 9. 12. 10:47

좋은 질문이에요 🙂 “Watchdog”은 **임베디드 시스템이나 서버 같은 전자 장치에서 시스템이 멈추거나 오동작할 때 자동으로 감지하고 리셋하는 장치(또는 소프트웨어 기능)**를 말합니다.


1. 기본 개념

  • 마치 강아지(Watchdog)가 집을 지키듯, 시스템이 정상 동작하는지 계속 감시합니다.
  • CPU나 프로그램이 멈추면(=강아지가 밥 못 받음), 일정 시간 안에 신호가 안 들어오면 “에러났다!” 하고 리셋을 걸어줍니다.

2. 동작 방식

  1. 타이머 기반
    • Watchdog은 내부 타이머를 가지고 있어요.
    • 프로그램이 정상적으로 돌고 있다면 주기적으로 "나는 살아있어!"라는 신호(=kick, feed)를 Watchdog에게 줍니다.
  2. 오류 감지
    • 만약 프로그램이 멈춰서 그 신호를 못 주면, 타이머가 만료됩니다.
  3. 자동 리셋
    • 타이머가 만료되면 Watchdog이 CPU를 강제로 리셋해서 다시 부팅시킵니다.

3. 비유

  • 집에 강아지: 일정 시간마다 주인이 밥 줘야 함(=시스템이 kick 신호 보냄).
  • 주인이 기절: 밥이 안 옴 → 강아지가 짖어서 집을 깨움(=시스템 리셋).

4. 실제 활용

  • MCU/임베디드: 자동차 ECU, IoT 기기, 의료기기 등 멈추면 안 되는 장치에 필수.
  • 서버: 커널 패닉, 시스템 멈춤 시 자동 재부팅.
  • Linux: /dev/watchdog 디바이스로 접근 가능.

✅ 정리
Watchdog = 시스템이 멈췄는지 감시하고, 멈추면 자동으로 재부팅하는 “안전 장치”


혹시 원하시면, 제가 **STM32 같은 MCU에서 Watchdog 코드 예시 (HAL 기반)**를 보여드릴까요?

 

좋아! STM32(HAL) 기준으로 IWDG(Independent WDG)WWDG(Window WDG) 두 가지 예제를 바로 쓸 수 있게 드릴게.


✅ IWDG (전원/클럭 독립, 가장 흔히 사용)

  • LSI(≈32 kHz)로 동작, 메인 클럭이 죽어도 살아요.
  • 타임아웃 계산: Tout ≈ (Reload+1) * Prescaler / LSI
    • Prescaler ∈ {4,8,16,32,64,128,256}
    • 예) LSI=32 kHz, Presc=64, Reload=1999 → (2000 * 64) / 32000 ≈ 4 s

1) CubeMX 설정(권장)

  • Peripherals → IWDG Enable, Prescaler/Reload 설정.

2) 코드 (자동 생성 + 주기적 갱신)

// main.c
#include "main.h"

IWDG_HandleTypeDef hiwdg;

static void MX_IWDG_Init(void)
{
  // Prescaler = 64, Reload = 1999  → 약 4초 타임아웃 (LSI=32kHz 가정)
  hiwdg.Instance = IWDG;
  hiwdg.Init.Prescaler = IWDG_PRESCALER_64;
  hiwdg.Init.Reload = 1999;
  HAL_IWDG_Init(&hiwdg);
}

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_IWDG_Init();            // IWDG 시작 (즉시 동작)

  while (1)
  {
    // 애플리케이션 로직...
    HAL_Delay(500);          // 500ms마다 feed (4초보다 충분히 짧게)
    HAL_IWDG_Refresh(&hiwdg);
  }
}

주의

  • IWDG는 시작하면 정지 불가(리셋 외). 타임아웃은 넉넉하게.
  • LSI 주파수 편차(±%)가 커요. 마진을 두고 주기 설정하세요.
  • 긴 블로킹 구간(플래시 쓰기, 저전력 등)에서도 주기적으로 Refresh 해야 함.

✅ FreeRTOS에서 안전하게 feed 하는 법

  • 전용 WDG 태스크를 만들고, 핵심 태스크들이 “하트비트”를 보내면 묶어서 Refresh.
  • 한 태스크라도 “멈추면” 하트비트가 끊겨 WDG가 리셋 → 진짜 헝걸림 감지.
// watchdog_task.c (개념 예시)
extern IWDG_HandleTypeDef hiwdg;

volatile uint32_t hb_bits = 0;
#define HB_TASK1 (1U<<0)
#define HB_TASK2 (1U<<1)
// ... 필요만큼 비트 정의

void Task1(void *arg) {
  for(;;){
    // 작업...
    xTaskNotifyStateClear(NULL);
    hb_bits |= HB_TASK1;
    vTaskDelay(pdMS_TO_TICKS(200));
  }
}

void Task2(void *arg) {
  for(;;){
    // 작업...
    hb_bits |= HB_TASK2;
    vTaskDelay(pdMS_TO_TICKS(300));
  }
}

void WatchdogTask(void *arg) {
  const uint32_t ALL = HB_TASK1 | HB_TASK2;
  for(;;){
    if ((hb_bits & ALL) == ALL) {
      HAL_IWDG_Refresh(&hiwdg);  // 모두 살아있으면 feed
      hb_bits = 0;               // 다음 윈도우를 위해 클리어
    }
    vTaskDelay(pdMS_TO_TICKS(500)); // IWDG 타임아웃보다 충분히 짧게
  }
}

✅ WWDG (Window Watchdog) 예시

  • APB 클럭 사용, “너무 빨라도/너무 늦어도” 리셋되는 윈도우형.
  • 타이밍 제어가 까다롭지만, 타이밍 이상도 잡아냄.
#include "main.h"

WWDG_HandleTypeDef hwwdg;

static void MX_WWDG_Init(void)
{
  // 예: PCLK1=84MHz, Presc=8 → counter 크기와 윈도우에 맞춰 설정
  hwwdg.Instance = WWDG;
  hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
  hwwdg.Init.Window    = 0x50;      // 윈도우 상한
  hwwdg.Init.Counter   = 0x7F;      // 시작 카운터
  hwwdg.Init.EWIMode   = WWDG_EWI_ENABLE; // 조기 경보 인터럽트(선택)
  HAL_WWDG_Init(&hwwdg);
}

void loop_feed_wwdg(void)
{
  // 반드시 윈도우 안에서만 refresh!
  // (실제론 타이머/틱 기준으로 정확히 맞춰 호출)
  HAL_WWDG_Refresh(&hwwdg);
}

  • WWDG는 정확한 주기가 포인트 → 타이머 인터럽트에서 Refresh.
  • 윈도우보다 빨리 Refresh 하면 바로 리셋됨.

🔍 부팅 원인 확인 (리셋 원인 로깅)

  • 일부 STM32는 RCC_CSR 레지스터의 WDG Reset Flag로 리셋 원인 확인 가능.
if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) {
  // 이전 리셋은 IWDG에 의해 발생
}
__HAL_RCC_CLEAR_RESET_FLAGS();

🧩 실전 체크리스트

  • 타임아웃은 여유 있게(LSI 편차 고려)
  • 블로킹 구간에도 feed 가능?
  • FreeRTOS면 하트비트 집계 방식으로 설계
  • 리셋 후 원인 로깅 남기기(UART/플래시에 카운터)
  • 디버깅 중엔 WDG 비활성(CubeMX 옵션/HAL_DBGMCU_EnableDBGStopMode) 고려

필요하시면 귀하가 쓰는 STM32 모델명(예: F4, L4, H7 등) 알려주세요. 그 칩의 정확한 LSI 주파수·타이머 계산값으로 타임아웃을 산출해서, 프로젝트에 바로 붙일 수 있는 설정값까지 계산해 드릴게요!

 

좋아요—이번엔 워치독(Watchdog) 을 한 단계 깊게, 하지만 이해되게 정리해볼게요.

워치독이란?

**시스템이 멈추거나(헝걸림), 무한 루프·데드락 등 ‘비정상 상태’에 빠지면 자동으로 감지해 리셋시키는 ‘안전장치’**예요.

  • 하드웨어 WDT(칩 내부 또는 외부 IC): 전용 타이머가 돌아가다 “기한 내에 신호(Feed/Kick)를 못 받으면” 즉시 리셋.
  • 소프트웨어 WDT: 커널/OS가 타이머로 응답 지연을 감지해 리커버리(프로세스 kill 등). 보조 개념으로 쓰고, 최후 보루는 HW WDT로 잡는 게 일반적입니다.

어떻게 동작하나? (기본 루프)

  1. WDT 타이머 시작 → 2) 애플리케이션이 주기적으로 Feed(=“나 살아있어!” 신호)
  2. 만약 멈추면 Feed 누락 → 4) 타이머 만료 → 5) 하드 리셋(재부팅)

용어: Feed/Pet/Kick(같은 뜻), Timeout(만료시간), Window(허용 갱신 구간)


유형별 특징

  • IWDG(Independent WDT): 메인 클럭과 독립된 저속 오실레이터로 동작 → 메인 클럭이 죽어도 리셋 가능(신뢰성 ↑).
  • WWDG(Window WDT): 너무 빨라도/늦어도 갱신하면 리셋(타이밍 이상까지 잡음).
  • 외부 슈퍼바이저 IC(예: 전원감시+BOD): 칩 밖에서 감시 → 전원 이상/잠김에도 강함, 이중화 용이.

설계 패턴 (중요!)

1) “하트비트 집계” 패턴 (RTOS/멀티스레드 권장)

여러 중요한 태스크가 **각자 비트 플래그(하트비트)**를 올리고, 감시 태스크가 모든 비트를 확인해 모두 OK일 때만 WDT를 Feed.

  • 장점: 한 태스크라도 멈추면 Feed가 멈춰 진짜 오류만 리셋 유발.
  • 안티패턴: 메인 루프 한 곳에서 무조건 Feed(→ 일부 태스크가 죽어도 리셋 못 함).

2) “윈도우”로 타이밍 이상 탐지

  • 너무 자주(빠르게) Feed하는 버그도 잡음(바쁘다고 계속 리셋을 미루다 무한 Feed 하는 경우).
  • 타이머 인터럽트에서 정확히 Feed하거나, 주기 검증을 넣어요.

3) “계측 + 로그”로 원인 추적

  • 리셋 원인 레지스터(예: IWDG 리셋 플래그)로 WDT 리셋 여부 기록.
  • 부팅 카운터/부팅 시간 로그를 플래시에 남겨 리셋 루프 감지 → 안전모드 진입(네트워킹 지연, 기능 축소 등).

4) 펌웨어 업데이트/부트로더 연동

  • OTA 중 멈추면 WDT가 자동 복구하게 설계.
  • 업데이트 단계에 따라 타임아웃을 넉넉히 조정하거나 일시 정지/특별 루틴 사용.

5) 저전력·장시간 블로킹 고려

  • 슬립/DEEP STOP 동안 Feed 불가 → 슬립 전 WDT 일시정지(가능한 칩만) 또는 타임아웃 확장.
  • 플래시 쓰기/암호화 같은 블로킹 구간이 타임아웃보다 길지 않게 설계.

타임아웃 계산 요령

  1. 가장 긴 주기 작업(센서 폴링, I/O, 파일쓰기) +
  2. 지터/스케줄링 여유 +
  3. 클럭 편차(내부 LSI 오실레이터 편차)
    → 합산 후 안전 마진(예: 1.5~3배) 넣어 설정.
  • IWDG는 내부 LSI 오차가 큽니다(수 %). 실측해서 마진을 넉넉히!

흔한 실수 (피하세요)

  • ISR(인터럽트)에서 Feed: 메인 쓰레드가 죽어도 인터럽트는 계속 살아 Feed되어 오류를 숨김.
  • 한 군데서 무조건 Feed: 일부 서브시스템 죽어도 감지 못함.
  • 디버깅 중 활성화: 브레이크로 멈춰있다 리셋 반복 → 디버그 모드에서 일시 비활성 필요.
  • 타임아웃 과소: 정상 동작도 리셋 → “불안정한 제품”이 됨.
  • 리셋 루프 방치: 부팅 직후 같은 코드 경로에서 또 WDT 만료 → 플래시/네트워크 소모만 커짐.

Linux에서의 Watchdog (사용자 공간)

커널이 노출하는 /dev/watchdog로 제어합니다.

간단 C 예시

#include <linux/watchdog.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>

int main() {
  int fd = open("/dev/watchdog", O_WRONLY);
  int timeout = 10; // 10초
  ioctl(fd, WDIOC_SETTIMEOUT, &timeout);

  while (1) {
    // 주기적으로 keepalive
    ioctl(fd, WDIOC_KEEPALIVE, 0);
    sleep(5);
  }

  // 일부 드라이버는 'V'를 쓰고 닫아야 리부트 방지
  write(fd, "V", 1);
  close(fd);
  return 0;
}

주의: 단순 close()가 즉시 리부트 트리거가 될 수 있음(드라이버마다 다름). “magic close”("V") 또는 WDIOC_SETOPTIONS로 DISABLE 후 종료.

systemd 통합

서비스 유닛에 WatchdogSec=15s 설정 후, 프로세스가 주기적으로 sd_notify("WATCHDOG=1") 보내면 systemd가 죽은 프로세스를 재시작.

  • 장점: 프로세스 단위 감시
  • 단점: 커널/하드락까지는 못 잡음 → 하드웨어 WDT와 병행 권장

MCU(예: STM32)에서의 Watchdog

  • IWDG: LSI로 구동, 메인 클럭 고장에도 작동. 개발 중엔 마진 크게.
  • WWDG: 윈도우 내에서만 Feed → 타이밍 이상까지 탐지.
  • 리셋 원인 확인:
  • if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) { // 이전 리셋이 IWDG 때문 } __HAL_RCC_CLEAR_RESET_FLAGS();

보안·안전 관점

  • Fail-safe 설계: 장애 발생 시 안전한 동작 상태(밸브 닫기, 모터 정지 등)로 전환되도록 부팅 로직/하드웨어 릴레이 설계.
  • 표준: 자동차(ISO 26262), 항공(DO-178C) 등은 독립 클럭 WDT + 외부 슈퍼바이저 조합, 부트 카운터/세이프모드 등 요구가 일반적.

요약

  • Watchdog = “살아있다” 신호를 못 받으면 시스템을 강제로 재부팅하는 최후의 안전장치.
  • 제대로 쓰려면 하트비트 집계, 윈도우 타이밍, 로그/세이프모드, 저전력·블로킹 고려가 핵심.
  • **HW WDT(칩 내부/외부)**로 최종 보장을 하고, 필요하면 systemd/소프트 WDT를 보조로 겹겹이 설계하면 신뢰성이 크게 올라갑니다.

원하시면, 지금 사용 중인 보드/OS(예: RV1106 Linux, T527 Android, STM32 등)를 알려주세요. 그 환경에 맞춘 구체 타임아웃 수치·초기화 코드·하트비트 설계도를 바로 맞춰 드릴게요.