Watchdog 서브시스템
Watchdog 서브시스템을 시스템 생존성 보장과 장애 자동 복구 관점에서 심층 정리합니다. watchdog_device/watchdog_ops 생명주기, timeout·pretimeout·nowayout 정책의 운영 의미, 하드웨어 워치독(iTCO 등)과 softdog 차이, userspace daemon과 커널 keepalive 협력 구조, pretimeout governor를 이용한 덤프(Dump) 수집, suspend/resume 중 watchdog 유지 전략, 오동작 리셋 루프 방지 설정, 현장 장애 재현과 검증 절차까지 고신뢰 시스템 구축에 필요한 핵심을 다룹니다.
핵심 요약
- watchdog_device 구조체(Struct) — id, timeout, pretimeout, min/max_timeout, status 비트를 포함하는 핵심 객체입니다.
- watchdog_ops 콜백(Callback) — 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 커널 워커 — 유저 프로세스(Process)가 파일을 닫아도 커널 내부 hrtimer가 ping을 계속 유지합니다.
- WDOG_HW_RUNNING 비트 — 부트로더(Bootloader)가 WDT를 활성화한 채로 커널에 진입할 때 이 비트를 설정하면 커널이 즉시 ping을 시작합니다.
- devm_watchdog_register_device() — devres 기반 등록으로 remove() 시 자동 해제됩니다.
- systemd 통합 — RuntimeWatchdogSec으로 PID 1이 HW watchdog을 관리하고, WatchdogSec으로 서비스별 liveness를 체크합니다.
단계별 이해
- 하드웨어 watchdog 확인
dmesg에서 "watchdog" 메시지와 /sys/class/watchdog/ 디렉터리로 HW watchdog 존재를 확인합니다. - watchdog_device 초기화
min/max_timeout, 기본 timeout, info, ops를 설정하고 devm_watchdog_register_device()로 등록합니다. - nowayout 전략 결정
프로덕션 임베디드는 nowayout=1, 개발/테스트 환경은 nowayout=0으로 시작합니다. - pretimeout 설정
kdump 연동을 원하면 pretimeout governor를 panic으로 설정하고 IRQ 핸들러(Handler)에서 watchdog_notify_pretimeout()을 호출합니다. - 사용자 공간(User Space) kick 전략
watchdog 데몬, systemd RuntimeWatchdogSec, 또는 직접 /dev/watchdog 중 환경에 맞는 방식을 선택합니다. - WDOG_HW_RUNNING 처리
부트로더가 WDT를 시작했으면 probe()에서 HW 레지스터(Register)를 확인하여 set_bit(WDOG_HW_RUNNING, ...) 합니다. - PM 통합 검증
suspend/resume 이후 watchdog이 정상 동작하는지 확인합니다. PM notifier 또는 드라이버 pm_ops에서 재초기화합니다. - 부팅 루프 방지
bootstatus로 이전 부팅이 WDT 리셋이었는지 확인하고, 반복 루프 시 안전 모드 진입 로직을 추가합니다.
Watchdog 서브시스템 개요
Watchdog 서브시스템(drivers/watchdog/)은 시스템이 비정상 상태(hang, deadlock, 커널 패닉(Kernel Panic) 미검출 등)에 빠졌을 때 하드웨어 또는 소프트웨어 타이머를 이용해 강제 리셋을 수행하는 프레임워크입니다. 사용자 공간 데몬이나 커널 스레드(Kernel Thread)가 주기적으로 watchdog을 "kick"(ping)하고, 일정 시간 안에 kick이 없으면 시스템을 리부팅합니다.
핵심 데이터 구조
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]; /* 드라이버 식별 문자열 */
};
코드 설명
id- IDA(ID Allocator)가 자동 부여하는 인스턴스 번호입니다.
id=0이면/dev/watchdog0이자 레거시/dev/watchdog으로 노출됩니다. infowatchdog_info포인터로, 유저 공간이WDIOC_GETSUPPORT를 호출할 때 이 구조체가 복사됩니다.identity문자열과options비트마스크가 핵심입니다.ops- 하드웨어 제어 콜백 테이블 포인터입니다.
start만 필수이며, 나머지는 프레임워크가 기본 동작을 제공합니다. bootstatus- 이전 부팅에서 watchdog 리셋이 발생했는지 나타내는
WDIOF_*비트 조합입니다. 부트 루프 방지 로직에서 이 값을 검사합니다. timeout / min_timeout / max_timeout- 현재 타임아웃과 하드웨어가 허용하는 범위입니다.
watchdog_register_device()가 자동으로 범위 클램핑을 수행합니다. min_hw_heartbeat_ms / max_hw_heartbeat_ms- 하드웨어 ping 간격의 최소/최대입니다. 너무 빈번한 kick이 하드웨어를 손상시키는 칩(예: 일부 eSPI 기반 EC)에서
min_hw_heartbeat_ms를 설정합니다.max_hw_heartbeat_ms가 설정되면 커널이 자동으로 keep_alive 워커를 활성화하여 소프트웨어 타임아웃을 하드웨어 한계 이상으로 확장합니다. status비트WDOG_ACTIVE(동작 중),WDOG_NO_WAY_OUT(nowayout),WDOG_HW_RUNNING(부트로더가 시작한 HW watchdog) 등 런타임 상태를 원자적으로 관리합니다.set_bit()/test_bit()으로 접근합니다.wd_data- 프레임워크 내부 전용 데이터(
watchdog_core_data)로, hrtimer, kthread_work, mutex, cdev 등을 포함합니다. 드라이버가 직접 접근하면 안 됩니다.
코드 설명
ownerTHIS_MODULE을 대입합니다. 유저가/dev/watchdog을 open하면try_module_get(ops->owner)로 모듈 언로드를 방지합니다.start(필수)- 하드웨어 타이머를 활성화합니다.
watchdog_start()→wdd->ops->start(wdd)경로로 호출되며, 성공 시WDOG_ACTIVE비트가 설정됩니다. stop- 하드웨어 타이머를 정지합니다. 없으면 Magic Close가 불가능하고, 유저가 close해도 커널 워커가 계속 ping합니다.
ping- 카운터만 리셋하는 최적 경로입니다.
NULL이면 프레임워크가stop()+start()로 대체하므로 성능 차이가 발생합니다. set_timeout- 하드웨어 레지스터에 새 타임아웃을 기록합니다. 콜백 내에서
wdd->timeout = new_value를 직접 갱신해야 합니다. 하드웨어가 이산적(discrete) 값만 지원하면 가장 가까운 값으로 설정 후 실제 값을wdd->timeout에 반영합니다. get_timeleft- 남은 시간을 초 단위로 반환합니다. 하드웨어 카운터를 클럭 주파수로 나누어 계산하는 것이 일반적입니다.
restart- watchdog을 이용한 시스템 재부팅입니다. 타임아웃을 최솟값으로 설정하고 start하여 빠른 리셋을 유도합니다.
register_restart_handler()에 연결됩니다. ioctl- 표준 watchdog ioctl 외에 드라이버 고유 명령을 처리합니다. 표준 명령은 프레임워크가 먼저 처리하고, 알 수 없는 명령만 이 콜백에 전달됩니다.
코드 설명
optionsWDIOF_*플래그의 비트 OR 조합입니다. 유저 공간이WDIOC_GETSUPPORT로 드라이버 기능을 탐색하는 데 사용합니다. 예를 들어WDIOF_PRETIMEOUT이 없으면WDIOC_SETPRETIMEOUT이-EOPNOTSUPP을 반환합니다.firmware_version- EC(Embedded Controller)나 BMC 기반 watchdog에서 펌웨어 리비전을 유저에 전달합니다. 대부분의 내장 watchdog은 0으로 설정합니다.
identity- 최대 32바이트 NUL 종료 문자열로,
wdctl이나watchdog데몬이 로그에 출력합니다.
| WDIOF_* 플래그 | 값 | 의미 |
|---|---|---|
WDIOF_OVERHEAT | 0x0001 | 과열로 인한 리셋 감지 |
WDIOF_FANFAULT | 0x0002 | 팬 장애 감지 |
WDIOF_EXTERN1/2 | 0x0004/0x0008 | 외부 릴레이 1/2 |
WDIOF_POWERUNDER | 0x0010 | 전압 저하 감지 |
WDIOF_CARDRESET | 0x0020 | 마지막 리부트가 watchdog에 의한 것 |
WDIOF_POWEROVER | 0x0040 | 전압 초과 감지 |
WDIOF_SETTIMEOUT | 0x0080 | 타임아웃 런타임 설정 가능 |
WDIOF_MAGICCLOSE | 0x0100 | Magic Close 기능 지원 |
WDIOF_PRETIMEOUT | 0x0200 | pretimeout 기능 지원 |
WDIOF_ALARMONLY | 0x0400 | 알람만 (리셋 없음) |
WDIOF_KEEPALIVEPING | 0x8000 | Keep Alive ping 지원 |
Watchdog ioctl 인터페이스
디바이스 노드와 기본 동작
사용자 공간에서 /dev/watchdog (또는 /dev/watchdogN)을 통해 watchdog을 제어합니다. 디바이스를 open()하면 watchdog이 시작되고, 파일에 아무 데이터든 write()하면 kick이 됩니다.
| ioctl 명령 | 방향 | 인자 타입 | 설명 |
|---|---|---|---|
WDIOC_GETSUPPORT | R | watchdog_info | 드라이버 정보 및 지원 기능 조회 |
WDIOC_GETSTATUS | R | int | 현재 상태 플래그 조회 |
WDIOC_GETBOOTSTATUS | R | int | 부팅 시 watchdog 리셋 여부 |
WDIOC_GETTEMP | R | int | 온도 읽기 (지원 시) |
WDIOC_SETOPTIONS | W | int | WDIOS_DISABLECARD / WDIOS_ENABLECARD |
WDIOC_KEEPALIVE | - | - | watchdog ping (kick) |
WDIOC_SETTIMEOUT | RW | int | 타임아웃 설정 (초), 실제 적용 값 반환 |
WDIOC_GETTIMEOUT | R | int | 현재 타임아웃 조회 |
WDIOC_SETPRETIMEOUT | RW | int | pretimeout 설정 (초) |
WDIOC_GETPRETIMEOUT | R | int | 현재 pretimeout 조회 |
WDIOC_GETTIMELEFT | R | int | 만료까지 남은 시간 (초) |
/* 사용자 공간에서 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 드라이버 구현
watchdog_ops 콜백 테이블
| 콜백 | 필수 | 호출 시점 | 설명 |
|---|---|---|---|
start | 필수 | open() 또는 ioctl(WDIOC_KEEPALIVE) | 하드웨어 타이머 시작 |
stop | 권장 | close() (Magic Close 시) | 하드웨어 타이머 정지 |
ping | 선택 | write() 또는 ioctl(WDIOC_KEEPALIVE) | 타이머 리셋 (없으면 stop+start) |
set_timeout | 선택 | ioctl(WDIOC_SETTIMEOUT) | 타임아웃 값 변경 |
set_pretimeout | 선택 | ioctl(WDIOC_SETPRETIMEOUT) | 프리타임아웃 값 변경 |
get_timeleft | 선택 | ioctl(WDIOC_GETTIMELEFT) | 남은 시간 조회 |
restart | 선택 | 시스템 재부팅 시 | 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>;
};
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=0 | nowayout=1 |
|---|---|---|
close() 전에 'V' write | watchdog 중지 | watchdog 계속 동작 |
close() 전에 'V' 없음 | 커널 워커가 ping 유지 | 커널 워커가 ping 유지 |
| 유저 프로세스 crash | 파일 자동 close → ping 유지 | 파일 자동 close → ping 유지 |
WDIOC_SETOPTIONS(DISABLECARD) | watchdog 중지 | EBUSY 에러 |
=y이면 모든 watchdog 드라이버의 기본 nowayout이 1이 됩니다. 프로덕션 임베디드 시스템에서는 강력히 권장되는 설정입니다. 개별 드라이버의 nowayout 모듈 파라미터로 오버라이드할 수 있습니다.
Pretimeout과 Governor
Pretimeout 개념
Pretimeout은 watchdog이 실제로 시스템을 리셋하기 전에 미리 알림(인터럽트(Interrupt)/NMI)을 발생시키는 기능입니다. 이 시점에서 로그를 남기거나 패닉을 유발하여 크래시 덤프를 확보할 수 있습니다.
/* 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 | 모듈 | 동작 |
|---|---|---|
| noop | pretimeout_noop.ko | pr_alert()로 로그만 출력 |
| panic | pretimeout_panic.ko | panic() 호출 → 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초 후 리셋
panic으로 설정하고 kdump를 구성하면, watchdog 만료 직전에 자동으로 크래시 덤프를 생성할 수 있습니다. 이는 hang 상태 디버깅에 매우 유용합니다.
Pretimeout 알림 흐름
정상 ping이 중단된 후 pretimeout과 최종 timeout까지의 전체 흐름을 보여줍니다. Governor 설정에 따라 pretimeout 시점의 동작이 달라집니다:
Pretimeout Governor 내부 구조
Pretimeout governor는 watchdog_governor 구조체를 통해 등록됩니다. 드라이버의 pretimeout 인터럽트 핸들러가 watchdog_notify_pretimeout()을 호출하면, 현재 등록된 governor의 pretimeout 콜백이 실행됩니다.
/* drivers/watchdog/watchdog_pretimeout.c — governor 인프라 */
struct watchdog_governor {
const char *name;
void (*pretimeout)(struct watchdog_device *wdd);
struct list_head list; /* 전역 governor 목록 */
};
/* drivers/watchdog/pretimeout_panic.c — panic governor 구현 */
static void pretimeout_panic(struct watchdog_device *wdd)
{
panic("watchdog%d: pretimeout event\\n", wdd->id);
}
static struct watchdog_governor watchdog_gov_panic = {
.name = "panic",
.pretimeout = pretimeout_panic,
};
/* watchdog_notify_pretimeout() — governor 디스패치 */
void watchdog_notify_pretimeout(struct watchdog_device *wdd)
{
if (!wdd->gov) {
pr_alert("watchdog%d: pretimeout, no governor\\n", wdd->id);
return;
}
/* 등록된 governor의 pretimeout 콜백 호출 */
wdd->gov->pretimeout(wdd);
}
코드 설명
watchdog_governor구조체- governor 이름과 pretimeout 콜백 함수 포인터를 포함합니다.
watchdog_register_governor()로 전역 리스트에 등록되며, sysfs를 통해 런타임에 governor를 전환할 수 있습니다. pretimeout_panic()panic()을 호출하여 커널 패닉을 발생시킵니다. kdump가 구성되어 있으면 크래시 덤프가 자동으로 생성됩니다. 프로덕션 서버에서 hang 상태의 원인 분석에 필수적인 governor입니다.watchdog_notify_pretimeout()- 드라이버의 pretimeout IRQ 핸들러에서 호출됩니다. governor가 등록되어 있지 않으면 로그만 출력하고 반환합니다. 이 함수는 인터럽트 컨텍스트에서 호출될 수 있으므로, governor 콜백은 sleep하면 안 됩니다.
- sysfs 인터페이스
pretimeout_governor에 governor 이름을 write하면watchdog_set_governor()가 호출되어wdd->gov를 변경합니다.pretimeout_available_governors는 등록된 모든 governor 이름을 공백으로 구분하여 출력합니다.
사용자 공간 활용
리눅스에서 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_wdt | Intel 칩셋 (ICH/PCH) | drivers/watchdog/iTCO_wdt.c | 가장 보편적인 서버/데스크톱 WDT |
sp5100_tco | AMD 칩셋 | drivers/watchdog/sp5100_tco.c | AMD 서버/데스크톱 WDT |
imx2_wdt | NXP i.MX SoC | drivers/watchdog/imx2_wdt.c | 임베디드 ARM SoC 대표 |
bcm2835_wdt | Broadcom (Raspberry Pi) | drivers/watchdog/bcm2835_wdt.c | RPi 보드 WDT |
softdog | 소프트웨어 (범용) | drivers/watchdog/softdog.c | HW 없이 테스트용, hrtimer 기반 |
mei_wdt | Intel ME | drivers/watchdog/mei_wdt.c | Intel Management Engine WDT |
omap_wdt | TI OMAP/AM335x | drivers/watchdog/omap_wdt.c | BeagleBone 등 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 콜백 구현 확인 |
Watchdog Core 내부 동작
커널 watchdog 프레임워크(drivers/watchdog/watchdog_core.c, watchdog_dev.c)는 char device, 내부 타이머, 동기화 메커니즘을 통해 사용자 공간과 하드웨어 사이를 중개합니다.
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.c | char device 생성, wd_data 할당, hrtimer 초기화 |
watchdog_ping() | watchdog_dev.c | ops→ping() 호출, 없으면 stop+start 시퀀스 |
watchdog_timer_expired() | watchdog_dev.c | hrtimer 콜백 → kthread 워크 큐에 ping_work 제출 |
watchdog_ping_work() | watchdog_dev.c | 실제 HW ping 수행 (mutex 보호) |
watchdog_start_hrtimer() | watchdog_dev.c | timeout/2 주기로 hrtimer 설정 |
watchdog_worker_should_ping() | watchdog_dev.c | ACTIVE 상태 & 유저 open 여부로 ping 필요성 판단 |
watchdog_restart_notifier() | watchdog_core.c | reboot notifier로 ops→restart() 호출 |
코드 설명
watchdog_register_device()의 내부 흐름을 단계별로 분석합니다.
- 1단계:
watchdog_check_min_max_timeout() min_timeout이max_timeout보다 큰지,max_hw_heartbeat_ms와 모순되는지 등을 검사합니다. 드라이버 개발자의 초기화 실수를 빠르게 차단합니다.- 2단계: timeout 범위 클램핑
wdd->timeout이[min_timeout, max_timeout]범위를 벗어나면 자동으로 조정합니다. Device Tree의timeout-sec프로퍼티도 이 시점에서 적용됩니다.- 3단계:
watchdog_dev_register() - 핵심 등록 함수입니다.
watchdog_core_data구조체를 할당하고,cdev_device_add()로/dev/watchdogN을 생성하며, hrtimer와 kthread_work를 초기화합니다. id=0이면 추가로/dev/watchdogmisc device도 등록합니다. - 4단계:
WDOG_HW_RUNNING처리 - 부트로더가 이미 watchdog을 시작한 경우,
watchdog_ping()을 호출하여 hrtimer를 시동합니다. 유저 공간 데몬이 준비되기 전에 타임아웃이 만료되는 것을 방지합니다. - 5단계: restart handler
ops->restart가 구현되어 있으면register_restart_handler()로 시스템 재부팅 경로에 등록합니다. 우선순위(priority)는restart_nb.priority로 제어합니다.
watchdog_dev_register() 호출 체인
/* drivers/watchdog/watchdog_dev.c — watchdog_dev_register() 핵심 흐름 */
int watchdog_dev_register(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data;
int err;
/* watchdog_core_data 할당 — cdev, hrtimer, lock 등 포함 */
wd_data = kzalloc(sizeof(*wd_data), GFP_KERNEL);
if (!wd_data)
return -ENOMEM;
mutex_init(&wd_data->lock);
wd_data->wdd = wdd;
wdd->wd_data = wd_data;
/* hrtimer 초기화 — keep_alive ping 타이머 */
hrtimer_init(&wd_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
wd_data->timer.function = watchdog_timer_expired;
/* kthread worker 초기화 — RT 우선순위로 ping 수행 */
kthread_init_work(&wd_data->ping_work, watchdog_ping_work);
/* cdev 초기화 및 디바이스 노드 생성 */
cdev_init(&wd_data->cdev, &watchdog_fops);
wd_data->cdev.owner = wdd->ops->owner;
/* /dev/watchdogN 생성 (major=동적, minor=wdd->id) */
wd_data->dev.devt = MKDEV(MAJOR(watchdog_devt), wdd->id);
wd_data->dev.class = &watchdog_class;
wd_data->dev.parent = wdd->parent;
dev_set_name(&wd_data->dev, "watchdog%d", wdd->id);
err = cdev_device_add(&wd_data->cdev, &wd_data->dev);
if (err) {
kfree(wd_data);
return err;
}
/* id=0이면 레거시 /dev/watchdog (misc device)도 등록 */
if (wdd->id == 0) {
old_wd_data = wd_data;
misc_register(&watchdog_miscdev);
}
return 0;
}
코드 설명
watchdog_core_data할당- 드라이버가 등록하는
watchdog_device와 1:1로 대응되는 내부 관리 구조체입니다. cdev, hrtimer, mutex, kthread_work 등 사용자-커널 인터페이스에 필요한 모든 상태를 보유합니다. hrtimer_init()CLOCK_MONOTONIC기반 고해상도 타이머를 초기화합니다.watchdog_timer_expired()콜백이 만료 시 호출되어 kthread 워크 큐에 ping 작업을 제출합니다. 주기는timeout / 2로, 유저 공간이 kick을 놓쳐도 하드웨어 리셋 전에 한 번 더 ping할 여유를 줍니다.cdev_device_add()- cdev와
struct device를 동시에 등록합니다. sysfs에/sys/class/watchdog/watchdogN이 생성되고, udev가/dev/watchdogN노드를 만듭니다. - id=0 misc device
- 하위 호환성을 위해 첫 번째 watchdog은
/dev/watchdog(major 10, minor 130)으로도 접근할 수 있습니다.old_wd_data전역 변수가 이 연결을 유지합니다.
watchdog_ping() 함수 구현 분석
watchdog_ping()은 사용자 공간의 write/ioctl과 커널 내부 keep_alive 워커 모두에서 호출되는 핵심 함수입니다. 하드웨어 ping 간격 제한(min_hw_heartbeat_ms)을 고려하고, hrtimer를 재설정하여 다음 자동 ping 주기를 갱신합니다.
/* drivers/watchdog/watchdog_dev.c — watchdog_ping() 핵심 경로 */
static int watchdog_ping(struct watchdog_device *wdd)
{
struct watchdog_core_data *wd_data = wdd->wd_data;
ktime_t earliest, now;
int err;
/* min_hw_heartbeat_ms 확인 — 너무 빈번한 ping 방지 */
if (wdd->min_hw_heartbeat_ms) {
now = ktime_get();
earliest = ktime_add_ms(wd_data->last_hw_keepalive,
wdd->min_hw_heartbeat_ms);
if (ktime_before(now, earliest)) {
/* 너무 이르면 hrtimer가 나중에 대신 ping */
watchdog_hrtimer_start(wdd);
return 0;
}
}
/* HW ping 수행 — ops->ping() 또는 stop()+start() */
if (wdd->ops->ping)
err = wdd->ops->ping(wdd);
else
err = wdd->ops->start(wdd); /* ping 없으면 start로 대체 */
if (!err)
wd_data->last_hw_keepalive = ktime_get();
/* hrtimer 재시작 — 다음 자동 ping 스케줄 */
watchdog_hrtimer_start(wdd);
return err;
}
코드 설명
min_hw_heartbeat_ms가드- 일부 하드웨어(특히 EC 기반 watchdog)는 너무 빈번한 kick을 허용하지 않습니다.
last_hw_keepalive타임스탬프와 비교하여, 최소 간격이 지나지 않았으면 실제 HW ping을 건너뛰고 hrtimer에 위임합니다. ops->ping()vsops->start()- 드라이버가
ping콜백을 제공하면 카운터만 리셋하는 최적 경로를 사용합니다. 없으면start()를 호출하여 전체 재초기화를 수행합니다. 성능 민감한 시스템에서는ping구현이 필수적입니다. watchdog_hrtimer_start()- 다음 자동 ping까지의 타이머를 재설정합니다.
max_hw_heartbeat_ms가 설정되어 있으면 그 값을, 아니면timeout * 1000 / 2ms를 사용합니다. 이를 통해 유저 공간 데몬이 kick을 놓쳐도 커널이 자동으로 HW를 유지합니다. last_hw_keepalivektime_get()으로 기록한 마지막 실제 HW ping 시각입니다.min_hw_heartbeat_ms가드와 디버깅에 사용됩니다.
watchdog_dev_open / write / ioctl 분석
사용자 공간이 /dev/watchdog을 통해 watchdog과 상호작용하는 세 가지 핵심 file_operations 콜백의 내부 동작을 분석합니다.
/* drivers/watchdog/watchdog_dev.c — open() 내부 흐름 */
static int watchdog_open(struct inode *inode, struct file *file)
{
struct watchdog_core_data *wd_data;
struct watchdog_device *wdd;
int err;
wd_data = container_of(inode->i_cdev,
struct watchdog_core_data, cdev);
/* 동시 open 방지 — 단일 사용자만 허용 */
if (test_and_set_bit(_WDOG_DEV_OPEN, &wd_data->status))
return -EBUSY;
wdd = wd_data->wdd;
/* 모듈 참조 카운트 증가 — rmmod 방지 */
if (!try_module_get(wdd->ops->owner)) {
err = -EBUSY;
goto out_clear;
}
err = watchdog_start(wdd); /* ops→start() 호출 */
if (err < 0)
goto out_mod;
file->private_data = wd_data;
watchdog_hrtimer_start(wdd); /* keep_alive 타이머 시동 */
return 0;
out_mod:
module_put(wdd->ops->owner);
out_clear:
clear_bit(_WDOG_DEV_OPEN, &wd_data->status);
return err;
}
/* ioctl 핵심 분기 — WDIOC_KEEPALIVE, SETTIMEOUT 등 */
static long watchdog_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
struct watchdog_core_data *wd_data = file->private_data;
struct watchdog_device *wdd = wd_data->wdd;
int __user *argp = (int __user *)arg;
int val, err;
mutex_lock(&wd_data->lock);
/* 드라이버 고유 ioctl을 먼저 시도 */
if (wdd->ops->ioctl) {
err = wdd->ops->ioctl(wdd, cmd, arg);
if (err != -ENOIOCTLCMD)
goto out;
}
switch (cmd) {
case WDIOC_GETSUPPORT:
err = copy_to_user(argp, wdd->info, sizeof(*wdd->info));
break;
case WDIOC_KEEPALIVE:
err = watchdog_ping(wdd);
break;
case WDIOC_SETTIMEOUT:
get_user(val, argp);
err = watchdog_set_timeout(wdd, val);
if (!err)
put_user(wdd->timeout, argp); /* 실제 적용 값 반환 */
break;
/* ... GETTIMEOUT, SETPRETIMEOUT, GETBOOTSTATUS 등 ... */
}
out:
mutex_unlock(&wd_data->lock);
return err;
}
코드 설명
test_and_set_bit(_WDOG_DEV_OPEN)- watchdog 디바이스는 동시에 하나의 프로세스만 열 수 있습니다. 원자적(atomic) 비트 연산으로 경합을 방지하며, 이미 열려 있으면
-EBUSY를 반환합니다. try_module_get()- watchdog이 열려 있는 동안 드라이버 모듈이 언로드되는 것을 방지합니다.
watchdog_release()에서module_put()으로 해제합니다. watchdog_start()- open() 시 자동으로 watchdog을 시작합니다. 이 시점부터 유저가 주기적으로 kick하지 않으면 타임아웃이 발생합니다.
WDOG_ACTIVE비트가 설정됩니다. - ioctl 우선순위
- 드라이버의
ops->ioctl이 먼저 호출됩니다.-ENOIOCTLCMD를 반환하면 프레임워크의 표준 처리로 넘어갑니다. 이를 통해 드라이버가 표준 명령을 오버라이드하거나 고유 명령을 추가할 수 있습니다. WDIOC_SETTIMEOUT반환 값- 하드웨어가 요청된 정확한 값을 지원하지 않을 수 있으므로, 실제 적용된
wdd->timeout값을put_user()로 유저에 반환합니다. - mutex 보호
wd_data->lockmutex가 모든 ioctl 경로를 직렬화합니다. hrtimer 콜백에서 호출되는watchdog_ping_work()도 같은 mutex를 사용하여 드라이버 콜백의 동시 호출을 방지합니다.
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)에 내장되어 있으며, 다단계 리셋 메커니즘을 제공합니다.
/* 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 v1 | ICH0~ICH5 | 0.6초 | 약 39초 | 구형 8비트 레지스터 |
| TCO v2 | ICH6~ICH9 | 1.0초 | 614초 | 16비트 레지스터 |
| TCO v3 | ICH10~PCH | 1.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→직접 리셋으로 동작
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_margin | 60초 | watchdog 타임아웃 (1~65535초) |
nowayout | CONFIG 기본값 | Magic Close 무시 여부 |
soft_panic | 0 | 만료 시 panic (1) 또는 emergency_restart (0) |
# 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가 커널 공간(Kernel Space)에서 너무 오래 선점(Preemption) 불가 상태로 있으면(softlockup) 또는 인터럽트조차 처리 못 하면(hardlockup) 감지합니다.
/* 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.c | drivers/watchdog/ |
|---|---|---|
| 목적 | CPU lockup 감지 (디버깅) | 시스템 리셋 (신뢰성) |
| 메커니즘 | hrtimer + SCHED_FIFO kthread + perf NMI | HW 타이머 카운트다운 |
| 트리거 | 커널 선점불가 또는 인터럽트 불가 | 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 Watchdog | IPMI/BMC Watchdog |
|---|---|---|
| 독립성 | SoC/칩셋 내장 (OS 의존적) | 완전 독립 BMC 프로세서 |
| OS hang 감지 | OS 커널이 ping 안 하면 감지 | BMC 자체가 판단 (OS 무관) |
| 전원 제어 | 리셋만 가능 | 리셋 / 파워오프 / 파워사이클 |
| 원격 관리 | 불가 | IPMI over LAN으로 원격 제어 |
| 인터페이스 | /dev/watchdog | IPMI 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 서비스 WDT | WatchdogSec (예: 30초) | 특정 서비스 응답 없음 | 서비스 재시작(Reboot) |
| L2: systemd RuntimeWatchdog | RuntimeWatchdogSec (예: 60초) | PID 1 hang (systemd) | 커널 panic → HW 리셋 |
| L3: SoC HW WDT | 짧음 (예: 10초) | 커널 자체 hang | HW 리셋 |
| L4: 외부 WDT 칩 (GPIO) | 매우 짧음 (예: 1초) | GPIO 신호 없음 | 전원 차단/리셋 |
| L5: IPMI/BMC WDT | 긴 폴백 (예: 5분) | 완전 전원 장애 | 파워사이클 + 알림 |
소프트 마진과 하드 마진 타이밍
소프트 마진(pretimeout)과 하드 마진(timeout)의 타이밍 관계를 이해하면 watchdog 정책을 효과적으로 설계할 수 있습니다. pretimeout은 최종 리셋 전 진단 시간을 확보합니다:
전원 관리(Power Management) 통합
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_SUSPEND | suspend 중 ping 완전 차단 | HW가 자동 정지되는 SoC |
WDOG_STOP_ON_REBOOT | reboot notifier에서 자동 stop | 리부트 중 리셋 방지 |
WDOG_STOP_ON_UNREGISTER | 모듈 언로드 시 자동 stop | 개발/테스트 드라이버 |
| PM notifier 기반 | 프레임워크가 suspend/resume 처리 | 간단한 드라이버 (권장) |
| 드라이버 pm_ops 직접 구현 | HW 재초기화 등 세밀한 제어 | 복잡한 HW 시퀀스 필요 시 |
WDOG_NO_PING_ON_SUSPEND와 함께 최대 타임아웃 설정이 필수입니다.
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.