Skip to content

커널 패닉 (Kernel Panic)

커널 패닉은 커널에서 복구 불가능한 오류를 만났을 때 발생합니다. Go나 Rust에서의 panic과 비슷한 개념입니다. 윈도우에서 가끔 보이는 “블루 스크린”을 떠올려보면 이해가 쉽죠. 우리의 OS에도 크리티컬한 오류가 발생했을 때 커널이 알아서 멈추도록 구현해봅시다.

다음 PANIC 매크로가 그 역할을 합니다.

kernel.h
c
#define PANIC(fmt, ...)                                                        \
    do {                                                                       \
        printf("PANIC: %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__);  \
        while (1) {}                                                           \
    } while (0)

이 매크로는 패닉이 발생한 소스 파일과 줄 번호를 출력한 뒤, 무한 루프에 빠져서 더 이상의 처리를 중단합니다. 매크로로 정의한 이유는 __FILE____LINE__이 제대로 호출 위치의 파일명과 줄 번호를 표시하도록 하기 위해서입니다. 만약 함수를 썼다면, 이 매개변수들은 함수가 정의된 위치(매크로 정의가 들어 있는 파일/라인)로 잘못 표시될 것입니다.

이 매크로에서는 아래 두 가지 유용한 기법을 사용하고 있습니다:

  1. do-while(0) 구조 : while (0)이므로 이 루프는 한 번만 실행됩니다. 여러 문장으로 이루어진 매크로를 do { ... } while (0)로 묶으면, 다른 구문(예: if 문)과 함께 사용할 때 의도치 않은 동작이 발생하는 것을 피할 수 있습니다. 그리고 각 줄 끝의 역슬래시(\)에 주의하세요. 매크로가 여러 줄에 걸쳐서 정의되어 있어도, 매크로 전개 시에는 줄바꿈이 무시됩니다.

  2. ##__VA_ARGS__ 문법 : 매개변수가 가변적인 매크로를 정의할 때 유용한 확장 기능입니다. (참고: GCC 문서) ##는 가변 인자가 비어 있을 경우, 불필요한 쉼표(,)를 제거해줍니다. 예를 들어 PANIC("booted!")처럼 인자가 하나뿐이어도 오류 없이 잘 컴파일되도록 해줍니다.

예시

PANIC을 한 번 사용해봅시다. 그냥, printf처럼 쓰면 됩니다:

kernel.c
c
void kernel_main(void) {
    memset(__bss, 0, (size_t) __bss_end - (size_t) __bss);

    PANIC("booted!");
    printf("unreachable here!\n");
}

QEMU에서 실행했을 때, 커널 패닉이 실제로 발생하고 아래처럼 정확한 파일명과 줄 번호가 찍히는지, 그리고 PANIC 뒤의 코드는 전혀 실행되지 않는지(즉, "unreachable here!"가 출력되지 않는지) 확인해보세요.

$ ./run.sh
PANIC: kernel.c:46: booted!

윈도우의 블루 스크린이나 리눅스의 커널 패닉처럼 한편으론 무서운 기능이지만, 우리가 만드는 커널 안에서는 사용자에게 원인을 알려주고 멈출 수 있는 “우아한 크래시” 메커니즘이라고 할 수 있습니다. 이를 통해 인간이 이해할 수 있는 오류 메시지를 띄우고 시스템이 안전하게 멈추도록 하는 것이죠.