Kernel Panic
内核恐慌(kernel panic)发生在内核遇到不可恢复的错误时,类似于 Go 或 Rust 中的 panic
概念。你是否在 Windows 上见过蓝屏?让我们在我们的内核中实现相同的概念来处理致命错误。
以下 PANIC
宏是内核恐慌的实现:
c
#define PANIC(fmt, ...) \
do { \
printf("PANIC: %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
while (1) {} \
} while (0)
它打印出恐慌发生的位置,然后进入一个无限循环来停止处理。我们在这里将其定义为宏。这样做的原因是为了正确显示源文件名(__FILE__
)和行号(__LINE__
)。如果我们将其定义为函数,__FILE__
和 __LINE__
将显示 PANIC
被定义的文件名和行号,而不是它被调用的位置。
这个宏还使用了两个惯用语:
第一个惯用语是 do-while
语句。由于它是 while (0)
,这个循环只执行一次。这是定义由多个语句组成的宏的常见方式。简单地用 { ...}
封装可能会在与 if
等语句组合时导致意外的行为(参见这个清晰的例子)。另外,注意每行末尾的反斜杠(\
)。虽然宏是在多行上定义的,但在展开时换行符会被忽略。
第二个惯用语是 ##__VA_ARGS__
。这是一个用于定义接受可变数量参数的宏的有用编译器扩展(参考:GCC 文档)。当可变参数为空时,##
会删除前面的 ,
。这使得即使只有一个参数,如 PANIC("booted!")
,编译也能成功。
让我们试试
让我们试试使用 PANIC
。你可以像使用 printf
一样使用它:
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!
Windows 的蓝屏和 Linux 的内核恐慌都很可怕,但在你自己的内核中,你不觉得这是一个很好的功能吗?这是一个“优雅崩溃”的机制,带有人类可读的提示。