Linux 커널은 printk()로 남긴 메시지를 ring buffer 형태로 저장하고, 이를 /dev/kmsg나 dmesg 명령으로 확인할 수 있게 해준다. 그런데 시스템이 패닉(panic)이나 크래시에 빠지면 이 로그가 디스크에 채 기록되기 전에 시스템이 멈춰버려 유실될 수 있다. kmsg_dump는 이런 상황에서도 로그 버퍼 내용을 UART, 플래시, RAM 같은 별도 매체에 덤프해 디버깅에 활용할 수 있게 해주는 커널 기능이다. 이 글에서는 kmsg_dump의 개념, 사용법, 예제 코드, 동작 방식, 주의사항을 알아본다.
kmsg_dump란?
kmsg_dump는 커널 패닉, 재부팅, 크래시 발생 시점에 printk 로그 버퍼의 내용을 안전한 저장 매체(메모리, 시리얼 포트, 플래시 메모리 등)에 덤프할 수 있도록 지원하는 기능이다.
특징:
- panic 컨텍스트에서도 안전하게 호출 가능하도록 설계됨
- 드라이버가 비블로킹(non-blocking) 방식으로 출력하는 것을 권장
- 수집한 로그를 UART, Flash, RAM 등 원하는 매체에 저장 가능
커널 크래시 디버깅과 원인 분석에 유용하며, 특히 화면/디스크 로그를 남기기 어려운 임베디드 시스템이나 원격 서버 환경에서 중요하게 쓰인다. 실제로 pstore 서브시스템이 이 메커니즘을 이용해 패닉 로그를 플래시나 NVRAM에 저장한다.
kmsg_dump 주요 인터페이스
구조체: struct kmsg_dumper
struct kmsg_dump_detail {
enum kmsg_dump_reason reason;
const char *description;
};
struct kmsg_dumper {
struct list_head list;
void (*dump)(struct kmsg_dumper *dumper, struct kmsg_dump_detail *detail);
enum kmsg_dump_reason max_reason;
bool registered;
};
dump 콜백은 과거 커널에서는 enum kmsg_dump_reason을 직접 받았지만, 현재는 reason과 설명 문자열을 담은 struct kmsg_dump_detail을 받는 형태로 바뀌었다. 커널 버전에 따라 시그니처가 다를 수 있으니 대상 커널의 헤더를 확인해야 한다.
주요 함수
| 함수 | 설명 |
|---|---|
kmsg_dump_register() | dumper 등록 |
kmsg_dump_unregister() | dumper 등록 해제 |
kmsg_dump_rewind(iter) | struct kmsg_dump_iter의 읽기 위치를 처음으로 초기화 |
kmsg_dump_get_line(iter, ...) | 한 줄씩 읽어오기 |
kmsg_dump_get_buffer(iter, ...) | 버퍼 단위로 읽기 |
rewind/get_line/get_buffer는 모두 호출자가 선언한 struct kmsg_dump_iter iter를 인자로 받아 읽기 위치를 추적한다.
kmsg_dump 동작 흐름
커널 패닉이 발생하면 panic_notifier_list를 거쳐 등록된 각 kmsg_dumper.dump()가 호출되고, 그 안에서 kmsg_dump_get_line()으로 로그를 한 줄씩 꺼내 원하는 출력 장치(UART 등)로 내보내는 흐름이다.

커널 모듈 예제: kmsg_dump 로그 수집
커널 패닉 시 printk 버퍼 내용을 읽어 다시 printk로 출력하는 간단한 예제다 (UART 콘솔이 연결돼 있으면 그 내용을 그대로 볼 수 있다).
모듈 코드: kmsg_dump_example.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kmsg_dump.h>
#include <linux/reboot.h>
#include <linux/fs.h>
static void my_kmsg_dump(struct kmsg_dumper *dumper,
struct kmsg_dump_detail *detail)
{
struct kmsg_dump_iter iter;
static char line[1024];
size_t len;
enum kmsg_dump_reason reason = detail->reason;
if (reason != KMSG_DUMP_PANIC && reason != KMSG_DUMP_OOPS) {
pr_info("kmsg_dump: skip reason=%d\n", reason);
return;
}
pr_info("kmsg_dump: start dumping lines (reason=%d)\n", reason);
kmsg_dump_rewind(&iter);
while (kmsg_dump_get_line(&iter, true, line, sizeof(line), &len)) {
line[len] = '\0';
printk("%s", line);
}
pr_info("kmsg_dump: completed\n");
}
static struct kmsg_dumper my_kmsg_dumper = {
.dump = my_kmsg_dump,
};
static int __init my_module_init(void)
{
kmsg_dump_register(&my_kmsg_dumper);
printk(KERN_INFO "kmsg_dumper registered.\n");
return 0;
}
static void __exit my_module_exit(void)
{
kmsg_dump_unregister(&my_kmsg_dumper);
printk(KERN_INFO "kmsg_dumper unregistered.\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("kmsg_dump Example");
Makefile
obj-m += kmsg_dump_example.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
커널 모듈 빌드 및 로딩
make
sudo insmod kmsg_dump_example.ko
로딩 시 dmesg에 아래 로그가 남는다.
[ 1856.569326] kmsg_dumper registered.
패닉 유발 테스트 (주의)
실제 장비나 중요한 데이터가 있는 환경이 아니라 테스트용 VM/보드에서만 실행한다. SysRq의 c는 강제로 커널 패닉을 유발한다.
echo c | sudo tee /proc/sysrq-trigger
예상 로그 (dmesg 또는 UART 출력)
--- Sending BREAK (SysRq) ---
[ 51.442359] sysrq: Trigger a crash
[ 51.445764] Kernel panic - not syncing: sysrq triggered crash
[ 51.451501] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Tainted: P O 6.12.0-332 #1
[ 51.459842] Tainted: [P]=PROPRIETARY_MODULE, [O]=OOT_MODULE
[ 51.465401] Hardware name: Raspberry Pi 3 Model B Rev 1.2 (DT)
[ 51.472090] Call trace:
[ 51.474524] dump_backtrace+0x94/0x114
[ 51.478267] show_stack+0x18/0x24
[ 51.481570] dump_stack_lvl+0x38/0x90
[ 51.485225] dump_stack+0x18/0x24
[ 51.488528] panic+0x38c/0x3e4
...
[ 52.930101] kmsg_dump: start dumping lines (reason=1)
...
[ 62.453802] kmsg_dump: completed