Watchdog 서브시스템

Watchdog 서브시스템을 시스템 생존성 보장과 장애 자동 복구 관점에서 심층 정리합니다. watchdog_device/watchdog_ops 생명주기, timeout·pretimeout·nowayout 정책의 운영 의미, 하드웨어 워치독(iTCO 등)과 softdog 차이, userspace daemon과 커널 keepalive 협력 구조, pretimeout governor를 이용한 덤프 수집, suspend/resume 중 watchdog 유지 전략, 오동작 리셋 루프 방지 설정, 현장 장애 재현과 검증 절차까지 고신뢰 시스템 구축에 필요한 핵심을 다룹니다.

전제 조건: 디바이스 드라이버전원관리 문서를 먼저 읽으세요. 제어형 디바이스는 안전 한계와 상태 전이가 중심이므로, 설정값의 적용 시점과 실패 복구 절차를 먼저 정리해야 합니다.
일상 비유: 이 주제는 설비 안전 제어반 운영과 비슷합니다. 온도/전압/타이머를 임계값 안에서 관리하듯이, 커널에서도 보호 경계와 즉시 복구 경로가 핵심입니다.

핵심 요약

  • watchdog_device 구조체 — id, timeout, pretimeout, min/max_timeout, status 비트를 포함하는 핵심 객체입니다.
  • watchdog_ops 콜백 — start/stop/ping/set_timeout/get_timeleft 다섯 가지가 핵심이며, ping이 없으면 기본적으로 stop+start로 대체됩니다.
  • nowayout 플래그 — 일단 활성화되면 Magic Close('V')조차 무시하고 watchdog을 절대 멈추지 않습니다. 임베디드 프로덕션에서 필수 설정입니다.
  • pretimeout governor — 리셋 전에 IRQ/NMI를 발생시켜 kdump로 크래시 덤프를 확보합니다. noop/panic 두 가지 governor가 있습니다.
  • keep_alive 커널 워커 — 유저 프로세스가 파일을 닫아도 커널 내부 hrtimer가 ping을 계속 유지합니다.
  • WDOG_HW_RUNNING 비트 — 부트로더가 WDT를 활성화한 채로 커널에 진입할 때 이 비트를 설정하면 커널이 즉시 ping을 시작합니다.
  • devm_watchdog_register_device() — devres 기반 등록으로 remove() 시 자동 해제됩니다.
  • systemd 통합 — RuntimeWatchdogSec으로 PID 1이 HW watchdog을 관리하고, WatchdogSec으로 서비스별 liveness를 체크합니다.

단계별 이해

  1. 하드웨어 watchdog 확인
    dmesg에서 "watchdog" 메시지와 /sys/class/watchdog/ 디렉터리로 HW watchdog 존재를 확인합니다.
  2. watchdog_device 초기화
    min/max_timeout, 기본 timeout, info, ops를 설정하고 devm_watchdog_register_device()로 등록합니다.
  3. nowayout 전략 결정
    프로덕션 임베디드는 nowayout=1, 개발/테스트 환경은 nowayout=0으로 시작합니다.
  4. pretimeout 설정
    kdump 연동을 원하면 pretimeout governor를 panic으로 설정하고 IRQ 핸들러에서 watchdog_notify_pretimeout()을 호출합니다.
  5. 사용자 공간 kick 전략
    watchdog 데몬, systemd RuntimeWatchdogSec, 또는 직접 /dev/watchdog 중 환경에 맞는 방식을 선택합니다.
  6. WDOG_HW_RUNNING 처리
    부트로더가 WDT를 시작했으면 probe()에서 HW 레지스터를 확인하여 set_bit(WDOG_HW_RUNNING, ...) 합니다.
  7. PM 통합 검증
    suspend/resume 이후 watchdog이 정상 동작하는지 확인합니다. PM notifier 또는 드라이버 pm_ops에서 재초기화합니다.
  8. 부팅 루프 방지
    bootstatus로 이전 부팅이 WDT 리셋이었는지 확인하고, 반복 루프 시 안전 모드 진입 로직을 추가합니다.
관련 표준: RS-232C (UART), SCSI-3, NVMe Specification — 입출력 장치 인터페이스 표준입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
관련 페이지: 기본 디바이스 드라이버 모델과 버스 서브시스템은 디바이스 드라이버, 버스 서브시스템 심화 페이지를 참고하세요.

Watchdog 서브시스템 개요

Watchdog 서브시스템(drivers/watchdog/)은 시스템이 비정상 상태(hang, deadlock, 커널 패닉 미검출 등)에 빠졌을 때 하드웨어 또는 소프트웨어 타이머를 이용해 강제 리셋을 수행하는 프레임워크입니다. 사용자 공간 데몬이나 커널 스레드가 주기적으로 watchdog을 "kick"(ping)하고, 일정 시간 안에 kick이 없으면 시스템을 리부팅합니다.

관련 페이지: softlockup/hardlockup 탐지 메커니즘(디버깅 관점)은 크래시 분석 페이지를 참고하세요. 이 섹션은 하드웨어 Watchdog 디바이스 드라이버와 커널 프레임워크에 집중합니다.
watchdog daemon open + ioctl + write systemd RuntimeWatchdogSec Application sd_notify("WATCHDOG=1") /dev/watchdog Character Device Watchdog Core (drivers/watchdog/watchdog_dev.c) watchdog_device · watchdog_ops · watchdog_info · pretimeout governor iTCO_wdt Intel TCO sp5100_tco AMD SP5100 imx2_wdt i.MX SoC softdog Software WDT ... 60+ platform drivers SoC WDT Register MMIO countdown TCO Timer Chipset I/O port BMC / IPMI WDT Out-of-band CPU NMI softdog hrtimer

핵심 데이터 구조

Watchdog 프레임워크의 핵심은 세 가지 구조체입니다: watchdog_device(드라이버가 등록하는 watchdog 인스턴스), watchdog_ops(하드웨어 제어 콜백), watchdog_info(유저 공간에 노출되는 정보).

/* include/linux/watchdog.h */

struct watchdog_device {
    int                     id;            /* watchdog 인스턴스 번호 (0 → /dev/watchdog0) */
    struct device          *parent;        /* 부모 디바이스 (platform_device.dev 등) */
    const struct watchdog_info  *info;   /* 유저 공간 노출 정보 (identity, options) */
    const struct watchdog_ops   *ops;    /* 하드웨어 제어 콜백 */
    const struct watchdog_governor *gov; /* pretimeout governor */
    unsigned int            bootstatus;    /* 부팅 시 watchdog 리셋 발생 여부 */
    unsigned int            timeout;       /* 현재 타임아웃 (초) */
    unsigned int            pretimeout;    /* pretimeout 값 (초, 0이면 비활성) */
    unsigned int            min_timeout;   /* 하드웨어 허용 최소 타임아웃 */
    unsigned int            max_timeout;   /* 하드웨어 허용 최대 타임아웃 */
    unsigned int            min_hw_heartbeat_ms; /* HW ping 최소 간격(ms) */
    unsigned int            max_hw_heartbeat_ms; /* HW 최대 허트비트(ms) */
    struct notifier_block  reboot_nb;     /* 리부트 notifier */
    struct notifier_block  restart_nb;    /* restart handler */
    struct notifier_block  pm_nb;         /* PM notifier */
    void                   *driver_data;   /* 드라이버 private 데이터 */
    struct watchdog_core_data *wd_data;  /* 코어 내부 데이터 (커널 전용) */
    unsigned long           status;        /* WDOG_* 상태 비트 */

    /* status 비트 플래그 */
#define WDOG_ACTIVE           0   /* watchdog 동작 중 */
#define WDOG_NO_WAY_OUT       1   /* nowayout: close해도 멈추지 않음 */
#define WDOG_STOP_ON_REBOOT   2   /* 리부트 시 자동 정지 */
#define WDOG_HW_RUNNING       3   /* HW watchdog이 자체 동작 중 */
#define WDOG_STOP_ON_UNREGISTER 4 /* unregister 시 정지 */
#define WDOG_NO_PING_ON_SUSPEND 5 /* suspend 중 ping 금지 */
};
/* Watchdog 하드웨어 제어 콜백 */
struct watchdog_ops {
    struct module *owner;
    int  (*start)(struct watchdog_device *);           /* HW 타이머 시작 (필수) */
    int  (*stop)(struct watchdog_device *);            /* HW 타이머 정지 */
    int  (*ping)(struct watchdog_device *);            /* 카운터 리셋 (kick) */
    unsigned int (*status)(struct watchdog_device *);  /* 상태 읽기 */
    int  (*set_timeout)(struct watchdog_device *, unsigned int); /* 타임아웃 변경 */
    int  (*set_pretimeout)(struct watchdog_device *, unsigned int); /* pretimeout 설정 */
    unsigned int (*get_timeleft)(struct watchdog_device *);  /* 남은 시간(초) */
    int  (*restart)(struct watchdog_device *, unsigned long, void *); /* 시스템 재시작 */
    long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); /* 커스텀 ioctl */
};
/* 유저 공간 노출 정보 — WDIOC_GETSUPPORT로 조회 */
struct watchdog_info {
    __u32 options;          /* WDIOF_* 기능 플래그 */
    __u32 firmware_version;  /* 펌웨어 버전 */
    __u8  identity[32];      /* 드라이버 식별 문자열 */
};
WDIOF_* 플래그의미
WDIOF_OVERHEAT0x0001과열로 인한 리셋 감지
WDIOF_FANFAULT0x0002팬 장애 감지
WDIOF_EXTERN1/20x0004/0x0008외부 릴레이 1/2
WDIOF_POWERUNDER0x0010전압 저하 감지
WDIOF_CARDRESET0x0020마지막 리부트가 watchdog에 의한 것
WDIOF_POWEROVER0x0040전압 초과 감지
WDIOF_SETTIMEOUT0x0080타임아웃 런타임 설정 가능
WDIOF_MAGICCLOSE0x0100Magic Close 기능 지원
WDIOF_PRETIMEOUT0x0200pretimeout 기능 지원
WDIOF_ALARMONLY0x0400알람만 (리셋 없음)
WDIOF_KEEPALIVEPING0x8000Keep Alive ping 지원

Watchdog ioctl 인터페이스

사용자 공간에서 /dev/watchdog (또는 /dev/watchdogN)을 통해 watchdog을 제어합니다. 디바이스를 open()하면 watchdog이 시작되고, 파일에 아무 데이터든 write()하면 kick이 됩니다.

ioctl 명령방향인자 타입설명
WDIOC_GETSUPPORTRwatchdog_info드라이버 정보 및 지원 기능 조회
WDIOC_GETSTATUSRint현재 상태 플래그 조회
WDIOC_GETBOOTSTATUSRint부팅 시 watchdog 리셋 여부
WDIOC_GETTEMPRint온도 읽기 (지원 시)
WDIOC_SETOPTIONSWintWDIOS_DISABLECARD / WDIOS_ENABLECARD
WDIOC_KEEPALIVE--watchdog ping (kick)
WDIOC_SETTIMEOUTRWint타임아웃 설정 (초), 실제 적용 값 반환
WDIOC_GETTIMEOUTRint현재 타임아웃 조회
WDIOC_SETPRETIMEOUTRWintpretimeout 설정 (초)
WDIOC_GETPRETIMEOUTRint현재 pretimeout 조회
WDIOC_GETTIMELEFTRint만료까지 남은 시간 (초)
/* 사용자 공간에서 watchdog 제어 예제 */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/watchdog.h>

int main(void)
{
    struct watchdog_info ident;
    int fd, timeout, timeleft;

    fd = open("/dev/watchdog", O_WRONLY);
    if (fd < 0) {
        perror("open /dev/watchdog");
        return 1;
    }

    /* 드라이버 정보 조회 */
    ioctl(fd, WDIOC_GETSUPPORT, &ident);
    printf("Watchdog: %s, options=0x%x\\n",
           ident.identity, ident.options);

    /* 타임아웃을 30초로 설정 */
    timeout = 30;
    ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
    printf("Timeout set to %d seconds\\n", timeout);

    /* 주기적으로 kick */
    for (;;) {
        ioctl(fd, WDIOC_KEEPALIVE, 0);

        ioctl(fd, WDIOC_GETTIMELEFT, &timeleft);
        printf("Time left: %d sec\\n", timeleft);

        sleep(10);
    }

    /* Magic Close로 안전하게 중지 */
    write(fd, "V", 1);
    close(fd);
    return 0;
}

Watchdog 드라이버 구현

최소한의 SoC watchdog 드라이버 구현 예제입니다. devm_watchdog_register_device()를 사용하면 자동으로 리소스가 해제됩니다.

/* drivers/watchdog/example_wdt.c — SoC Watchdog 드라이버 예제 */
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/watchdog.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/of.h>

/* 하드웨어 레지스터 오프셋 */
#define WDT_CR      0x00    /* Control Register */
#define WDT_TORR    0x04    /* Timeout Range Register */
#define WDT_CRR     0x0C    /* Counter Restart (kick) */
#define WDT_STAT    0x10    /* Interrupt Status */
#define WDT_EOI     0x14    /* Interrupt Clear */

#define WDT_CR_EN   BIT(0)   /* Watchdog Enable */
#define WDT_CR_RMOD BIT(1)   /* Response Mode: 0=reset, 1=irq+reset */
#define WDT_CRR_KICK 0x76   /* Magic kick value */

#define WDT_MIN_TIMEOUT  1
#define WDT_MAX_TIMEOUT  256
#define WDT_DEFAULT_TIMEOUT 30

static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0444);
MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");

struct example_wdt {
    struct watchdog_device  wdd;
    void __iomem           *base;
    struct clk             *clk;
    unsigned long          rate;
};

static inline struct example_wdt *to_example_wdt(struct watchdog_device *wdd)
{
    return container_of(wdd, struct example_wdt, wdd);
}

static int example_wdt_start(struct watchdog_device *wdd)
{
    struct example_wdt *wdt = to_example_wdt(wdd);
    u32 val;

    val = readl(wdt->base + WDT_CR);
    val |= WDT_CR_EN;
    writel(val, wdt->base + WDT_CR);

    return 0;
}

static int example_wdt_stop(struct watchdog_device *wdd)
{
    struct example_wdt *wdt = to_example_wdt(wdd);
    u32 val;

    val = readl(wdt->base + WDT_CR);
    val &= ~WDT_CR_EN;
    writel(val, wdt->base + WDT_CR);

    return 0;
}

static int example_wdt_ping(struct watchdog_device *wdd)
{
    struct example_wdt *wdt = to_example_wdt(wdd);

    writel(WDT_CRR_KICK, wdt->base + WDT_CRR);
    return 0;
}

static int example_wdt_set_timeout(struct watchdog_device *wdd,
                                   unsigned int timeout)
{
    struct example_wdt *wdt = to_example_wdt(wdd);
    u32 count;

    /* 클럭 주파수 기반으로 카운트 값 계산 */
    count = timeout * wdt->rate;
    writel(count, wdt->base + WDT_TORR);

    wdd->timeout = timeout;
    return 0;
}

static unsigned int example_wdt_get_timeleft(struct watchdog_device *wdd)
{
    struct example_wdt *wdt = to_example_wdt(wdd);
    u32 count;

    count = readl(wdt->base + WDT_TORR);
    return count / wdt->rate;
}

static const struct watchdog_info example_wdt_info = {
    .options  = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
                WDIOF_MAGICCLOSE,
    .identity = "Example SoC Watchdog",
};

static const struct watchdog_ops example_wdt_ops = {
    .owner        = THIS_MODULE,
    .start        = example_wdt_start,
    .stop         = example_wdt_stop,
    .ping         = example_wdt_ping,
    .set_timeout  = example_wdt_set_timeout,
    .get_timeleft = example_wdt_get_timeleft,
};

static int example_wdt_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct example_wdt *wdt;
    int ret;

    wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
    if (!wdt)
        return -ENOMEM;

    /* MMIO 레지스터 매핑 */
    wdt->base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(wdt->base))
        return PTR_ERR(wdt->base);

    /* 클럭 획득 및 활성화 */
    wdt->clk = devm_clk_get_enabled(dev, NULL);
    if (IS_ERR(wdt->clk))
        return PTR_ERR(wdt->clk);

    wdt->rate = clk_get_rate(wdt->clk);
    if (!wdt->rate)
        return -EINVAL;

    /* watchdog_device 초기화 */
    wdt->wdd.info        = &example_wdt_info;
    wdt->wdd.ops         = &example_wdt_ops;
    wdt->wdd.min_timeout = WDT_MIN_TIMEOUT;
    wdt->wdd.max_timeout = WDT_MAX_TIMEOUT;
    wdt->wdd.timeout     = WDT_DEFAULT_TIMEOUT;
    wdt->wdd.parent      = dev;

    /* nowayout 설정 */
    watchdog_set_nowayout(&wdt->wdd, nowayout);

    /* 부팅 시 이미 동작 중인 HW watchdog 처리 */
    if (readl(wdt->base + WDT_CR) & WDT_CR_EN)
        set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);

    /* devm 기반 등록 — remove 시 자동 해제 */
    ret = devm_watchdog_register_device(dev, &wdt->wdd);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, wdt);
    dev_info(dev, "watchdog registered (timeout=%ds, nowayout=%d)\\n",
             wdt->wdd.timeout, nowayout);

    return 0;
}

static const struct of_device_id example_wdt_of_match[] = {
    { .compatible = "vendor,example-wdt" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, example_wdt_of_match);

static struct platform_driver example_wdt_driver = {
    .probe  = example_wdt_probe,
    .driver = {
        .name           = "example-wdt",
        .of_match_table = example_wdt_of_match,
    },
};
module_platform_driver(example_wdt_driver);

MODULE_AUTHOR("Kernel Developer");
MODULE_DESCRIPTION("Example SoC Watchdog Driver");
MODULE_LICENSE("GPL");

Device Tree 바인딩 예제:

/* arch/arm64/boot/dts/vendor/example.dtsi */

watchdog@40010000 {
    compatible = "vendor,example-wdt";
    reg = <0x40010000 0x1000>;
    clocks = <&clk_wdt>;
    timeout-sec = <30>;
};
devm_watchdog_register_device(): 이 함수를 사용하면 platform_driver.remove()에서 별도로 watchdog_unregister_device()를 호출할 필요가 없습니다. 디바이스 모델의 devres 메커니즘이 자동으로 해제합니다.

Magic Close와 Nowayout

Watchdog은 한 번 활성화되면 주기적으로 kick하지 않으면 시스템을 리셋합니다. Magic Close는 파일을 닫기 전에 'V' 문자를 write하면 watchdog을 안전하게 중지할 수 있는 메커니즘입니다. Nowayout이 설정되면 어떤 방법으로도 watchdog을 중지할 수 없습니다.

/* drivers/watchdog/watchdog_dev.c — Magic Close 처리 */

static ssize_t watchdog_write(struct file *file,
        const char __user *data, size_t len, loff_t *ppos)
{
    struct watchdog_core_data *wd_data = file->private_data;
    size_t i;
    char c;

    if (len == 0)
        return 0;

    /* 'V' 문자가 있는지 스캔 */
    wd_data->expect_close = 0;
    for (i = 0; i < len; i++) {
        if (get_user(c, data + i))
            return -EFAULT;
        if (c == 'V')
            wd_data->expect_close = 42; /* magic value */
    }

    /* watchdog kick 수행 */
    watchdog_ping(wd_data->wdd);
    return len;
}

static int watchdog_release(struct inode *inode, struct file *file)
{
    struct watchdog_core_data *wd_data = file->private_data;
    struct watchdog_device *wdd = wd_data->wdd;

    if (!test_bit(WDOG_NO_WAY_OUT, &wdd->status) &&
        wd_data->expect_close == 42) {
        /* Magic Close: watchdog 중지 허용 */
        watchdog_stop(wdd);
    } else {
        /* 강제 유지: 커널 워커가 대신 ping 계속 */
        pr_crit("watchdog%d: not stopping!\\n", wdd->id);
        watchdog_ping(wdd);
    }
    return 0;
}
조건nowayout=0nowayout=1
close() 전에 'V' writewatchdog 중지watchdog 계속 동작
close() 전에 'V' 없음커널 워커가 ping 유지커널 워커가 ping 유지
유저 프로세스 crash파일 자동 close → ping 유지파일 자동 close → ping 유지
WDIOC_SETOPTIONS(DISABLECARD)watchdog 중지EBUSY 에러
CONFIG_WATCHDOG_NOWAYOUT: 커널 빌드 시 이 옵션이 =y이면 모든 watchdog 드라이버의 기본 nowayout이 1이 됩니다. 프로덕션 임베디드 시스템에서는 강력히 권장되는 설정입니다. 개별 드라이버의 nowayout 모듈 파라미터로 오버라이드할 수 있습니다.

Pretimeout과 Governor

Pretimeout은 watchdog이 실제로 시스템을 리셋하기 전에 미리 알림(인터럽트/NMI)을 발생시키는 기능입니다. 이 시점에서 로그를 남기거나 패닉을 유발하여 크래시 덤프를 확보할 수 있습니다.

t KICK t=0 PRETIMEOUT IRQ t=timeout-pretimeout SYSTEM RESET t=timeout 정상 동작 구간 (kick 기대) pretimeout 구간
/* pretimeout 콜백 구현 예제 */

static irqreturn_t example_wdt_pretimeout_irq(int irq, void *data)
{
    struct watchdog_device *wdd = data;

    /* watchdog core에 pretimeout 이벤트 전달
     * → 등록된 governor가 처리 */
    watchdog_notify_pretimeout(wdd);

    return IRQ_HANDLED;
}

/* probe()에서 pretimeout 설정 */
static int example_wdt_probe(struct platform_device *pdev)
{
    /* ... 기존 초기화 ... */
    int irq;

    irq = platform_get_irq(pdev, 0);
    if (irq > 0) {
        ret = devm_request_irq(dev, irq,
                example_wdt_pretimeout_irq, 0,
                "example-wdt-pretimeout", &wdt->wdd);
        if (!ret) {
            /* pretimeout 지원 활성화 */
            wdt->wdd.info->options |= WDIOF_PRETIMEOUT;

            /* Response Mode를 IRQ+Reset으로 설정 */
            val = readl(wdt->base + WDT_CR);
            val |= WDT_CR_RMOD;
            writel(val, wdt->base + WDT_CR);
        }
    }
    /* ... 등록 ... */
}
Pretimeout Governor모듈동작
nooppretimeout_noop.kopr_alert()로 로그만 출력
panicpretimeout_panic.kopanic() 호출 → kdump로 크래시 덤프 확보
# Governor 설정 방법

# 현재 governor 확인
cat /sys/class/watchdog/watchdog0/pretimeout_governor

# 사용 가능한 governor 목록
cat /sys/class/watchdog/watchdog0/pretimeout_available_governors

# governor 변경
echo panic > /sys/class/watchdog/watchdog0/pretimeout_governor

# pretimeout 설정 (예: 10초 전 알림)
# timeout=30, pretimeout=10 → 20초 후 IRQ, 30초 후 리셋
kdump와 결합: pretimeout governor를 panic으로 설정하고 kdump를 구성하면, watchdog 만료 직전에 자동으로 크래시 덤프를 생성할 수 있습니다. 이는 hang 상태 디버깅에 매우 유용합니다.

사용자 공간 활용

리눅스에서 watchdog을 활용하는 대표적인 방법은 전용 데몬(watchdog)을 사용하거나, systemd의 내장 watchdog 기능을 이용하는 것입니다.

# /etc/watchdog.conf — watchdog 데몬 설정

watchdog-device    = /dev/watchdog
watchdog-timeout   = 30
interval           = 10

# 시스템 모니터링 옵션
max-load-1         = 24       # 1분 로드 평균 임계값
max-load-5         = 18       # 5분 로드 평균 임계값
min-memory         = 1        # 최소 가용 메모리 (페이지)
max-temperature    = 90       # 최대 온도 (°C, HWMON)

# 파일 존재 확인 (NFS 마운트 등)
file               = /var/run/important_service.pid

# 핑 테스트 (네트워크 연결 확인)
ping               = 192.168.1.1
ping               = 10.0.0.1

# 인터페이스 확인
interface          = eth0

# 사용자 정의 테스트 스크립트
test-binary        = /usr/local/bin/health_check.sh
test-timeout       = 10
# systemd watchdog 통합

# /etc/systemd/system.conf — 하드웨어 watchdog
[Manager]
RuntimeWatchdogSec=30     # HW watchdog 타임아웃 (PID 1이 /dev/watchdog ping)
RebootWatchdogSec=10min   # 재부팅 중 watchdog 타임아웃
KExecWatchdogSec=0        # kexec 중 watchdog (0=비활성)
WatchdogDevice=/dev/watchdog0  # 사용할 watchdog 디바이스

# 서비스 단위 watchdog — /etc/systemd/system/myapp.service
[Service]
Type=notify
WatchdogSec=60            # 60초마다 sd_notify 기대
WatchdogSignal=SIGABRT    # 실패 시 보낼 시그널
Restart=on-watchdog       # watchdog 실패 시 재시작
ExecStart=/usr/bin/myapp
/* 애플리케이션에서 systemd watchdog 통합 */
#include <systemd/sd-daemon.h>

int main(void)
{
    uint64_t usec;
    int wd_enabled;

    /* systemd watchdog 활성화 여부 및 간격 확인 */
    wd_enabled = sd_watchdog_enabled(0, &usec);
    if (wd_enabled > 0) {
        printf("Watchdog enabled, interval=%llu us\\n", usec);
    }

    /* 서비스 준비 완료 알림 */
    sd_notify(0, "READY=1");

    for (;;) {
        /* 애플리케이션 메인 작업 수행 */
        do_work();

        /* 정상 동작 중이면 watchdog ping */
        if (health_check_ok())
            sd_notify(0, "WATCHDOG=1");

        /* WatchdogSec / 2 간격으로 호출 권장 */
        usleep(usec / 2);
    }

    sd_notify(0, "STOPPING=1");
    return 0;
}
방법장점단점적합한 환경
watchdog 데몬 시스템 전체 모니터링 (CPU, 메모리, 온도, 네트워크) 별도 패키지 설치 필요, 세밀한 앱 모니터링 어려움 임베디드 시스템, 서버
systemd HW watchdog PID 1이 직접 관리, 추가 설치 불필요 PID 1 hang 시에만 동작, 서비스 수준 모니터링 별도 필요 systemd 기반 범용 시스템
systemd 서비스 watchdog 서비스별 세밀한 liveness 체크 애플리케이션 코드 수정 필요 (sd_notify) 미션 크리티컬 서비스
직접 /dev/watchdog 완전한 제어, 의존성 없음 데몬 구현 필요, 실수 시 시스템 리셋 위험 커스텀 임베디드

Watchdog 디버깅

Watchdog 관련 문제 진단은 sysfs 인터페이스, dmesg 로그, 하드웨어 상태 레지스터를 통해 수행합니다.

# sysfs 인터페이스 — /sys/class/watchdog/watchdog0/

cat /sys/class/watchdog/watchdog0/identity     # 드라이버 이름
cat /sys/class/watchdog/watchdog0/timeout      # 현재 타임아웃 (초)
cat /sys/class/watchdog/watchdog0/timeleft     # 만료까지 남은 시간
cat /sys/class/watchdog/watchdog0/bootstatus   # 부팅 시 리셋 원인
cat /sys/class/watchdog/watchdog0/status       # 현재 상태 (active 등)
cat /sys/class/watchdog/watchdog0/nowayout     # nowayout 설정 여부
cat /sys/class/watchdog/watchdog0/state        # active / inactive
cat /sys/class/watchdog/watchdog0/pretimeout   # pretimeout 값

# dmesg에서 watchdog 관련 로그 확인
dmesg | grep -i watchdog
dmesg | grep -i "iTCO_wdt\|sp5100_tco\|softdog"

# 로드된 watchdog 모듈 확인
lsmod | grep wdt
lsmod | grep watchdog

# watchdog 디바이스 목록
ls -la /dev/watchdog*
드라이버플랫폼소스 위치비고
iTCO_wdtIntel 칩셋 (ICH/PCH)drivers/watchdog/iTCO_wdt.c가장 보편적인 서버/데스크톱 WDT
sp5100_tcoAMD 칩셋drivers/watchdog/sp5100_tco.cAMD 서버/데스크톱 WDT
imx2_wdtNXP i.MX SoCdrivers/watchdog/imx2_wdt.c임베디드 ARM SoC 대표
bcm2835_wdtBroadcom (Raspberry Pi)drivers/watchdog/bcm2835_wdt.cRPi 보드 WDT
softdog소프트웨어 (범용)drivers/watchdog/softdog.cHW 없이 테스트용, hrtimer 기반
mei_wdtIntel MEdrivers/watchdog/mei_wdt.cIntel Management Engine WDT
omap_wdtTI OMAP/AM335xdrivers/watchdog/omap_wdt.cBeagleBone 등 TI SoC
문제증상원인해결 방법
부팅 후 즉시 리셋 반복 부팅 루프, 콘솔에 watchdog timeout 로그 부트로더가 WDT 활성화 후 커널이 제때 ping 못함 부트로더에서 WDT 비활성화하거나 WDOG_HW_RUNNING 설정 확인
watchdog close 후에도 리셋 데몬 종료 수초 후 시스템 리셋 nowayout=1 또는 Magic Close 미수행 nowayout 파라미터 확인, close 전 'V' write
/dev/watchdog 열기 실패 EBUSY 또는 ENODEV 다른 프로세스가 이미 점유, 또는 드라이버 미로드 fuser /dev/watchdog으로 확인, 모듈 로드 확인
타임아웃 설정이 원하는 값과 다름 WDIOC_SETTIMEOUT 반환값이 요청값과 불일치 HW가 특정 단계만 지원 (예: 1,2,4,8,...초) 반환된 실제 값 사용, 드라이버 소스에서 지원 범위 확인
suspend/resume 후 watchdog 동작 안함 resume 후 ping 무응답 PM 콜백에서 HW 재초기화 누락 드라이버의 suspend/resume 콜백 구현 확인
크로스 레퍼런스: softlockup과 hardlockup 탐지기는 watchdog 프레임워크와 밀접하게 관련되어 있지만, 커널 내부 디버깅 메커니즘에 해당합니다. 자세한 내용은 크래시 분석 페이지의 lockup detector 섹션을 참고하세요.

Watchdog Core 내부 동작

커널 watchdog 프레임워크(drivers/watchdog/watchdog_core.c, watchdog_dev.c)는 char device, 내부 타이머, 동기화 메커니즘을 통해 사용자 공간과 하드웨어 사이를 중개합니다.

watchdog_dev.c — 상태 전이 & 내부 타이머 INACTIVE !WDOG_ACTIVE ACTIVE WDOG_ACTIVE set EXPIRED HW RESET 발생 open() / ops→start() timeout 초과 write('V')+close() & !nowayout keep_alive hrtimer watchdog_ping_work() — 주기: timeout/2 유저 close 후에도 ping 유지 활성화 시 시작 watchdog_core_data wdd: *watchdog_device timer: hrtimer lock: mutex expect_close: int char device /dev/watchdog (id=0) /dev/watchdog0 miscdevice 또는 cdev (watchdog_misc) 참조: drivers/watchdog/watchdog_dev.c · watchdog_core.c · include/linux/watchdog.h

watchdog_core.c 핵심 흐름

/* drivers/watchdog/watchdog_core.c — 등록 시 내부 초기화 */

int watchdog_register_device(struct watchdog_device *wdd)
{
    int ret;

    /* ① 유효성 검사 */
    ret = watchdog_check_min_max_timeout(wdd);
    if (ret)
        return ret;

    /* ② timeout 범위 클램핑 */
    if (wdd->timeout < wdd->min_timeout)
        wdd->timeout = wdd->min_timeout;
    if (wdd->max_timeout && wdd->timeout > wdd->max_timeout)
        wdd->timeout = wdd->max_timeout;

    /* ③ watchdog_core_data 할당 및 hrtimer 초기화 */
    ret = watchdog_dev_register(wdd);
    if (ret)
        return ret;

    /* ④ 이미 HW가 동작 중이면 즉시 keep_alive 시작 */
    if (test_bit(WDOG_HW_RUNNING, &wdd->status))
        watchdog_ping(wdd);    /* hrtimer 시동 */

    /* ⑤ restart handler 등록 */
    if (wdd->ops->restart) {
        wdd->restart_nb.notifier_call = watchdog_restart_notifier;
        register_restart_handler(&wdd->restart_nb);
    }

    return 0;
}

/* hrtimer 콜백 — 주기적 ping 유지 */
static enum hrtimer_restart watchdog_timer_expired(struct hrtimer *timer)
{
    struct watchdog_core_data *wd_data;

    wd_data = container_of(timer, struct watchdog_core_data, timer);
    kthread_queue_work(wd_data->kworker, &wd_data->ping_work);
    return HRTIMER_NORESTART;
}

/* ping_work 워커 — 실제 HW ping 수행 */
static void watchdog_ping_work(struct kthread_work *work)
{
    struct watchdog_core_data *wd_data;
    struct watchdog_device *wdd;

    wd_data = container_of(work, struct watchdog_core_data, ping_work);
    wdd = wd_data->wdd;

    if (!wdd)
        return;

    mutex_lock(&wd_data->lock);
    if (watchdog_worker_should_ping(wd_data))
        watchdog_ping(wdd);
    mutex_unlock(&wd_data->lock);
}
내부 함수소스 위치역할
watchdog_dev_register()watchdog_dev.cchar device 생성, wd_data 할당, hrtimer 초기화
watchdog_ping()watchdog_dev.cops→ping() 호출, 없으면 stop+start 시퀀스
watchdog_timer_expired()watchdog_dev.chrtimer 콜백 → kthread 워크 큐에 ping_work 제출
watchdog_ping_work()watchdog_dev.c실제 HW ping 수행 (mutex 보호)
watchdog_start_hrtimer()watchdog_dev.ctimeout/2 주기로 hrtimer 설정
watchdog_worker_should_ping()watchdog_dev.cACTIVE 상태 & 유저 open 여부로 ping 필요성 판단
watchdog_restart_notifier()watchdog_core.creboot notifier로 ops→restart() 호출

Char Device 등록 메커니즘

watchdog 프레임워크는 두 가지 char device 경로를 제공합니다. id=0인 첫 번째 watchdog은 /dev/watchdog(misc device, major 10)으로도 접근할 수 있고, 모든 watchdog은 /dev/watchdogN(major 248)으로 접근합니다.

/* watchdog char device 등록 순서 */

/* 1. misc device (레거시 호환 — /dev/watchdog) */
static struct miscdevice watchdog_miscdev = {
    .minor = WATCHDOG_MINOR,  /* 130 */
    .name  = "watchdog",
    .fops  = &watchdog_fops,
};

/* 2. 새로운 방식 — /dev/watchdog0, /dev/watchdog1, ... */
/*    watchdog_dev_register()에서 cdev_add()로 등록 */
static dev_t watchdog_devt;  /* alloc_chrdev_region()으로 동적 할당 */

/* 파일 오퍼레이션 */
static const struct file_operations watchdog_fops = {
    .owner          = THIS_MODULE,
    .llseek         = no_llseek,
    .write          = watchdog_write,       /* ping + 'V' 감지 */
    .unlocked_ioctl = watchdog_ioctl,
    .compat_ioctl   = compat_ptr_ioctl,
    .open           = watchdog_open,        /* watchdog 시작 */
    .release        = watchdog_release,     /* Magic Close 처리 */
};

iTCO_wdt 심층 분석

iTCO_wdt(drivers/watchdog/iTCO_wdt.c)는 Intel 칩셋의 TCO(Total Cost of Ownership) 타이머를 사용하는 가장 보편적인 x86 watchdog 드라이버입니다. ICH(I/O Controller Hub) 또는 PCH(Platform Controller Hub)에 내장되어 있으며, 다단계 리셋 메커니즘을 제공합니다.

Intel TCO Watchdog 아키텍처 (ICH/PCH) PMC / LPC Controller (PCI Device) lpc_ich (MFD) → ACPIBASE + SMBase + TCOBase TCOBase 레지스터 TCO_TMR (+0x01): timeout값 TCO_DAT_IN (+0x02): kick 레지스터 TCO1_STS (+0x04): 1차 만료 상태 TCO2_STS (+0x06): 2차 만료 상태 TCO1_CNT (+0x08): 제어 레지스터 TCO_WDSTATUS (+0x0C): bootstatus RLD (+0x00): reload kick TCOBase = ACPIBASE + 0x60 단위: 0.6초 틱 1단계: 타이머 만료 → SMI 생성 TCO_TMR 카운트다운 완료 → SMI (System Management Interrupt) 2단계: SMI 미처리 → 재카운트 OS가 SMI 핸들러에서 kick 안 하면 타이머 재시작 3단계: 2차 만료 → RESET 하드웨어 리셋 신호 (CF9h 레지스터 또는 RCIN#) ⚠ SMI_TCO1_EN bit(TCO1_CNT[13]): SMI 비활성화 시 1단계 건너뛰고 바로 2단계로
/* drivers/watchdog/iTCO_wdt.c — 핵심 레지스터 접근 */

/* ping (카운터 리로드) */
static int iTCO_wdt_ping(struct watchdog_device *wd_dev)
{
    struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);

    spin_lock(&p->io_lock);

    iTCO_vendor_pre_keepalive(p->smi_res, wd_dev->timeout);

    /* TCO Timer Reload: 특정 값 0x01을 RLD에 쓰면 카운터 리셋 */
    outw(0x01, TCO_RLD(p));

    spin_unlock(&p->io_lock);
    return 0;
}

/* 타임아웃 설정 */
static int iTCO_wdt_set_timeout(struct watchdog_device *wd_dev,
                                 unsigned int t)
{
    struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
    unsigned int val;
    unsigned long tmrval;

    /* 틱 단위로 변환: 1틱 = 0.6초 (TCO v1) 또는 1초 (TCO v2+) */
    tmrval = seconds_to_ticks(p, t);

    spin_lock(&p->io_lock);

    if (p->iTCO_version == 3) {
        outl(tmrval, TCO_TMR(p));
        val = inl(TCO_TMR(p));
    } else if (p->iTCO_version >= 2) {
        outw(tmrval, TCO_TMR(p));
        val = inw(TCO_TMR(p));
    } else {
        outb(tmrval, TCO_TMR(p));
        val = inb(TCO_TMR(p));
    }

    spin_unlock(&p->io_lock);
    wd_dev->timeout = ticks_to_seconds(p, val);
    return 0;
}
TCO 버전칩셋 세대틱 단위최대 타임아웃비고
TCO v1ICH0~ICH50.6초약 39초구형 8비트 레지스터
TCO v2ICH6~ICH91.0초614초16비트 레지스터
TCO v3ICH10~PCH1.0초614초32비트, PMBASE 방식 변경
# iTCO_wdt 동작 확인

# 드라이버 로드 확인
lsmod | grep iTCO
modinfo iTCO_wdt

# TCO 레지스터 직접 읽기 (루트 필요)
# ACPIBASE는 보통 0x400 또는 0x500에 위치 (lspci로 확인)
lspci -v | grep -i "SMBus\|LPC"

# dmesg에서 iTCO 초기화 로그 확인
dmesg | grep -i "iTCO\|TCO"
# 예: [    2.384] iTCO_wdt: Intel TCO WatchDog Timer Driver v1.11
# 예: [    2.385] iTCO_wdt: Found a Intel PCH TCO device (Version=3, TCOBASE=0x0460)

# bootstatus: 이전 리셋이 TCO watchdog에 의한 것인지 확인
cat /sys/class/watchdog/watchdog0/bootstatus
# 0: 정상 부팅  ≠0: WDT 리셋

# 가능하면 SMI 비활성화 (테스트 전용 — 프로덕션 사용 금지)
# iTCO 드라이버는 부팅 시 SMI_TCO_EN을 비활성화하여 SMI→직접 리셋으로 동작
iTCO & lockdown: 일부 BIOS는 TCO 타이머를 잠가 놓아(GBL_SMI_EN=1) 커널 드라이버가 제어하지 못하게 합니다. 이 경우 iTCO_wdt는 로드는 되지만 /dev/watchdog이 생성되지 않을 수 있습니다. CONFIG_ITCO_WDT_LIST_NOGPIO로 우회 가능합니다.

softdog 내부 구현

softdog(drivers/watchdog/softdog.c)은 별도 하드웨어 없이 커널 hrtimer만으로 동작하는 소프트웨어 watchdog입니다. 하드웨어 WDT가 없는 가상 머신이나 개발 환경에서 주로 사용합니다.

/* drivers/watchdog/softdog.c — 핵심 구현 */

static struct hrtimer softdog_fire_timer;
static unsigned long softdog_fire_time;  /* 만료 절대 시간 (jiffies) */

static int softdog_ping(struct watchdog_device *w)
{
    if (!hrtimer_active(&softdog_fire_timer))
        return 0;

    /* hrtimer를 현재 시각 + timeout으로 재설정 */
    hrtimer_forward_now(&softdog_fire_timer,
                        ktime_set(w->timeout, 0));
    return 0;
}

static int softdog_start(struct watchdog_device *w)
{
    hrtimer_start(&softdog_fire_timer,
                  ktime_set(w->timeout, 0),
                  HRTIMER_MODE_REL);
    return 0;
}

static int softdog_stop(struct watchdog_device *w)
{
    hrtimer_cancel(&softdog_fire_timer);
    return 0;
}

/* hrtimer 콜백 — 시스템 리셋 또는 패닉 */
static enum hrtimer_restart softdog_fire(struct hrtimer *timer)
{
    if (softdog_panic) {
        panic("Software Watchdog Timeout - %s\\n", current->comm);
    } else {
        pr_crit("Initiating system reboot\\n");
        emergency_restart();
    }
    return HRTIMER_NORESTART;
}

static int __init watchdog_init(void)
{
    hrtimer_init(&softdog_fire_timer, CLOCK_MONOTONIC,
                 HRTIMER_MODE_REL);
    softdog_fire_timer.function = softdog_fire;

    return platform_driver_register(&softdog_driver);
}
파라미터기본값설명
soft_margin60초watchdog 타임아웃 (1~65535초)
nowayoutCONFIG 기본값Magic Close 무시 여부
soft_panic0만료 시 panic (1) 또는 emergency_restart (0)
VM 환경 주의: softdog은 VM 일시 정지(pause) 시 hrtimer가 멈추므로, 호스트가 VM을 멈춘 동안 watchdog이 만료되지 않습니다. 이는 장점이자 단점입니다. 실제 시스템 hang을 감지하려면 하드웨어 WDT를 사용해야 합니다.
# softdog 사용법

# 모듈 로드 (기본 60초 타임아웃)
modprobe softdog

# panic 모드로 로드 (만료 시 kdump 트리거)
modprobe softdog soft_panic=1

# 타임아웃 120초, nowayout 활성화
modprobe softdog soft_margin=120 nowayout=1

# 확인
cat /sys/class/watchdog/watchdog0/identity
# Software Watchdog

Lockup Detector와 Watchdog

리눅스 커널의 Lockup Detector(kernel/watchdog.c)는 watchdog 드라이버 프레임워크(drivers/watchdog/)와 이름은 같지만 별도 메커니즘입니다. CPU가 커널 공간에서 너무 오래 선점 불가 상태로 있으면(softlockup) 또는 인터럽트조차 처리 못 하면(hardlockup) 감지합니다.

Lockup Detector vs HW Watchdog 비교 Softlockup Detector hrtimer: 4초마다 타임스탬프 갱신 watchdog/N kthread: SCHED_FIFO 최고 우선순위 임계: watchdog_thresh (기본 10초) 감지: 커널 선점 불가 스핀 (interrupt OK) 결과: BUG() 또는 panic (softlockup_panic=1) Hardlockup Detector PMU 하드웨어 카운터: CPU cycle 기반 NMI NMI 핸들러에서 hrtimer 갱신 확인 임계: watchdog_thresh*2 (기본 20초) 감지: 인터럽트 불가 무한 루프 결과: panic() 필수 (NMI에서만 감지 가능) HW Watchdog (drivers/watchdog/) 독립 하드웨어 타이머 — CPU hang이어도 동작 kernel/watchdog.c와 별개 — 상호 보완 관계 pretimeout → panic → kdump → HW reset 연쇄 CONFIG_LOCKUP_DETECTOR + CONFIG_HARDLOCKUP_DETECTOR
/* kernel/watchdog.c — Softlockup 감지 로직 */

/* hrtimer 콜백: 각 CPU마다 주기적으로 타임스탬프 업데이트 */
static enum hrtimer_restart watchdog_timer_fn(struct hrtimer *hrtimer)
{
    unsigned long touch_ts = watchdog_touch_ts();
    int duration;

    /* watchdog kthread가 최근에 실행되었는지 확인 */
    if (period_ts(touch_ts)) {
        /* SCHED_FIFO kthread가 실행되지 않음 → softlockup 후보 */
        duration = is_softlockup(touch_ts);
        if (duration) {
            pr_emerg("BUG: soft lockup - CPU#%d stuck for %us!\\n",
                     smp_processor_id(), duration);
            print_modules();
            dump_stack();
            if (softlockup_panic)
                panic("softlockup: hung tasks");
        }
    }
    /* hrtimer 재시작: 4초 주기 */
    hrtimer_forward_now(hrtimer, ns_to_ktime(sample_period));
    return HRTIMER_RESTART;
}

/* Hardlockup: NMI 핸들러 (arch/x86/kernel/nmi.c) */
static int hardlockup_detector_nmi_handler(unsigned int type,
        struct pt_regs *regs)
{
    if (is_hardlockup()) {
        pr_emerg("Watchdog detected hard LOCKUP on cpu %d\\n",
                 this_cpu());
        panic("Hard LOCKUP");
    }
    return NMI_DONE;
}
# Lockup Detector 제어

# 임계 시간 변경 (기본 10초)
echo 20 > /proc/sys/kernel/watchdog_thresh

# softlockup 감지 시 panic 활성화
echo 1 > /proc/sys/kernel/softlockup_panic

# hardlockup 감지 시 panic (기본: 1)
echo 1 > /proc/sys/kernel/hardlockup_panic

# watchdog 완전 비활성화 (디버깅 중 유용)
echo 0 > /proc/sys/kernel/watchdog

# CPU별 lockup detector 활성/비활성
echo 0 > /proc/sys/kernel/watchdog_cpumask  # 모든 CPU 비활성
echo f > /proc/sys/kernel/watchdog_cpumask  # CPU 0-3만 활성

# perf NMI 카운터 기반 hardlockup 확인
dmesg | grep "perf event create"
cat /sys/devices/system/cpu/cpu0/perf_event_mux_interval_ms
구분kernel/watchdog.cdrivers/watchdog/
목적CPU lockup 감지 (디버깅)시스템 리셋 (신뢰성)
메커니즘hrtimer + SCHED_FIFO kthread + perf NMIHW 타이머 카운트다운
트리거커널 선점불가 또는 인터럽트 불가ping 타임아웃
결과BUG/panic/dump (복구 시도)하드웨어 리셋 (강제)
CPU hang 시hardlockup NMI로 감지독립 HW로 여전히 동작
설정/proc/sys/kernel/watchdog*/sys/class/watchdog/
상호작용pretimeout panic → kdump 후 HW WDT 리셋lockup_detector가 panic 트리거

IPMI/BMC Out-of-band Watchdog

IPMI Watchdog(drivers/char/ipmi/ipmi_watchdog.c)는 서버의 BMC(Baseboard Management Controller)에 내장된 watchdog을 제어합니다. OS와 독립된 별도 프로세서가 관리하므로 OS 커널 자체가 hang되어도 시스템을 리셋할 수 있습니다.

# IPMI watchdog 설정 (ipmitool)

# 현재 WDT 설정 확인
ipmitool mc watchdog get

# WDT 설정: 300초, 만료 시 리셋
ipmitool mc watchdog set action=reset interval=300

# 수동 kick
ipmitool mc watchdog reset

# WDT 정지
ipmitool mc watchdog off

# 커널 드라이버 활성화 확인
modprobe ipmi_watchdog
modprobe ipmi_si     # IPMI 시스템 인터페이스

# 모듈 파라미터 — 타임아웃 60초, 만료 시 리셋
modprobe ipmi_watchdog timeout=60 action=reset pretimeout=10
/* IPMI Set Watchdog Timer 명령 구조
 * IPMI 2.0 Spec Section 27.7 */

struct ipmi_set_wdt_cmd {
    u8 timer_use;          /* 0x44 = SMS/OS WDT, Don't Stop */
    u8 timer_actions;      /* 0x01 = timeout → reset */
                           /* 0x02 = timeout → power down */
                           /* 0x03 = timeout → power cycle */
    u8 pretimeout;         /* pretimeout 인터벌 (초) */
    u8 timer_use_flags;    /* 만료 플래그 클리어 비트 */
    u16 initial_countdown; /* 타임아웃 (100ms 단위) */
};
특성OS HW WatchdogIPMI/BMC Watchdog
독립성SoC/칩셋 내장 (OS 의존적)완전 독립 BMC 프로세서
OS hang 감지OS 커널이 ping 안 하면 감지BMC 자체가 판단 (OS 무관)
전원 제어리셋만 가능리셋 / 파워오프 / 파워사이클
원격 관리불가IPMI over LAN으로 원격 제어
인터페이스/dev/watchdogIPMI KCS/BT/SSIF + /dev/watchdog
주요 용도임베디드, 데스크톱서버, 데이터센터

Multi-stage Stacked Watchdog

Stacked watchdog 패턴은 하드웨어 WDT와 소프트웨어 WDT를 계층적으로 쌓아 더 세밀한 감지와 복구를 구현합니다. 커널은 gpio_wdt 드라이버로 GPIO 출력을 통해 외부 watchdog 칩(MAX6370, DS1232 등)을 제어합니다.

/* drivers/watchdog/gpio_wdt.c — GPIO 기반 외부 WDT */

struct gpio_wdt_priv {
    struct gpio_desc        *gpiod;    /* WDI (Watchdog Input) GPIO */
    bool                     state;    /* 현재 GPIO 출력 상태 */
    enum gpio_wdt_hw_algo    hw_algo;  /* toggle / level 방식 */
    struct watchdog_device   wdd;
    struct timer_list        timer;    /* 커널 내부 ping 타이머 */
    bool                     running;
};

static int gpio_wdt_ping(struct watchdog_device *wdd)
{
    struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);

    if (priv->hw_algo == HW_ALGO_TOGGLE) {
        /* WDI 핀을 토글하여 외부 WDT 칩에 kick 신호 */
        priv->state = !priv->state;
        gpiod_set_value_cansleep(priv->gpiod, priv->state);
    } else {
        /* Level 방식: 짧은 펄스 */
        gpiod_set_value_cansleep(priv->gpiod, 0);
        udelay(1);
        gpiod_set_value_cansleep(priv->gpiod, 1);
    }

    /* 내부 타이머 재설정 */
    mod_timer(&priv->timer,
              jiffies + wdd->timeout * HZ / 2);
    return 0;
}
/* Device Tree — Multi-stage Watchdog 설정 예 */

/* 외부 MAX6370 WDT 칩 (GPIO 기반) */
watchdog-gpio {
    compatible = "linux,wdt-gpio";
    gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
    hw_algo = "toggle";
    hw_margin_ms = <1000>;          /* 외부 WDT 타임아웃: 1초 */
    timeout-sec = <60>;             /* 소프트웨어 관점 타임아웃 */
    always-running;                   /* probe 후 항상 동작 */
};

/* 내부 SoC WDT */
watchdog@40010000 {
    compatible = "vendor,soc-wdt";
    reg = <0x40010000 0x1000>;
    timeout-sec = <10>;             /* 짧은 타임아웃으로 커널 hang 빠르게 감지 */
};
계층타임아웃감지 대상액션
L1: systemd 서비스 WDTWatchdogSec (예: 30초)특정 서비스 응답 없음서비스 재시작
L2: systemd RuntimeWatchdogRuntimeWatchdogSec (예: 60초)PID 1 hang (systemd)커널 panic → HW 리셋
L3: SoC HW WDT짧음 (예: 10초)커널 자체 hangHW 리셋
L4: 외부 WDT 칩 (GPIO)매우 짧음 (예: 1초)GPIO 신호 없음전원 차단/리셋
L5: IPMI/BMC WDT긴 폴백 (예: 5분)완전 전원 장애파워사이클 + 알림

전원 관리 통합

Watchdog과 시스템 전원 관리(PM)는 복잡하게 상호작용합니다. suspend 중에는 ping이 불가능하고, resume 후에는 HW 타이머가 재초기화되어야 합니다.

/* drivers/watchdog/watchdog_dev.c — PM notifier */

static int watchdog_pm_notifier(struct notifier_block *nb,
        unsigned long action, void *data)
{
    struct watchdog_core_data *wd_data;
    struct watchdog_device *wdd;
    int ret = 0;

    wd_data = container_of(nb, struct watchdog_core_data, pm_nb);
    wdd = wd_data->wdd;

    switch (action) {
    case PM_HIBERNATION_PREPARE:
    case PM_SUSPEND_PREPARE:
        /* suspend 직전: hrtimer 정지 */
        if (!test_bit(WDOG_NO_PING_ON_SUSPEND, &wdd->status)) {
            watchdog_stop_hrtimer(wd_data);
            watchdog_update_worker(wdd);
        }
        break;

    case PM_POST_HIBERNATION:
    case PM_POST_SUSPEND:
        /* resume 후: HW 재초기화 후 ping 재개 */
        if (watchdog_active(wdd) || watchdog_hw_running(wdd)) {
            watchdog_ping(wdd);
        }
        break;
    }
    return notifier_from_errno(ret);
}

/* 드라이버가 직접 PM ops를 구현하는 경우 */
static int example_wdt_suspend(struct device *dev)
{
    struct example_wdt *wdt = dev_get_drvdata(dev);

    if (watchdog_active(&wdt->wdd)) {
        /* WDOG_NO_PING_ON_SUSPEND가 없으면 타임아웃을 최대로 늘려
         * suspend 동안 리셋이 발생하지 않도록 함 */
        example_wdt_set_timeout(&wdt->wdd, wdt->wdd.max_timeout);
        example_wdt_ping(&wdt->wdd);
    }
    return 0;
}

static int example_wdt_resume(struct device *dev)
{
    struct example_wdt *wdt = dev_get_drvdata(dev);

    if (watchdog_active(&wdt->wdd)) {
        /* 원래 타임아웃 복원 후 ping */
        example_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
        example_wdt_ping(&wdt->wdd);
    }
    return 0;
}

static const struct dev_pm_ops example_wdt_pm_ops = {
    .suspend = example_wdt_suspend,
    .resume  = example_wdt_resume,
    SET_SYSTEM_SLEEP_PM_OPS(example_wdt_suspend, example_wdt_resume)
};
플래그 / 설정의미언제 사용
WDOG_NO_PING_ON_SUSPENDsuspend 중 ping 완전 차단HW가 자동 정지되는 SoC
WDOG_STOP_ON_REBOOTreboot notifier에서 자동 stop리부트 중 리셋 방지
WDOG_STOP_ON_UNREGISTER모듈 언로드 시 자동 stop개발/테스트 드라이버
PM notifier 기반프레임워크가 suspend/resume 처리간단한 드라이버 (권장)
드라이버 pm_ops 직접 구현HW 재초기화 등 세밀한 제어복잡한 HW 시퀀스 필요 시
suspend-to-disk (hibernation): 히버네이션 복원 후 커널이 재초기화되지 않으므로, WDT를 사용하는 시스템에서 히버네이션은 매우 주의해야 합니다. 복원 과정 중 WDT 타임아웃이 발생할 수 있습니다. WDOG_NO_PING_ON_SUSPEND와 함께 최대 타임아웃 설정이 필수입니다.

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.