カーネルパニックは、カーネルが続行不能なエラーに遭遇したときに発生するもので、GoやRustでいうpanic
に似た概念です。次のPANIC
マクロがカーネルパニックの実装です。
kernel.h#define PANIC(fmt, ...) \ do { \ printf("PANIC: %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ while (1) {} \ } while (0)
エラー内容を表示した後に無限ループに入って処理を停止します。ここではマクロとして定義しています。なぜかというと、ソースファイル名 (__FILE__
) と行番号 (__LINE__
) を正しく表示するためです。これを関数として定義すると、__FILE__
と __LINE__
は、PANIC
の呼び出し元ではなく、PANIC
が定義されているファイル名と行番号になってしまいます。
このマクロでは、2つのイディオムが登場しています。
1つ目は do-while
文です。while (0)
であることから、このループは必ず1回しか実行されません。これは、複数の文からなるマクロを定義したいときに頻出する書き方です。単に { ...}
で括ってしまうと、if
文などと組み合わせたときに意図しない動作に繋がる (分かりやすい例) ことがあります。また、行末のバックスラッシュ (\
) にも注意してください。マクロは複数行で定義されていますが、展開時には改行が無視されます。
2つ目のイディオムは ##__VA_ARGS__
です。これは可変長引数を受け取るマクロを定義するときに便利なコンパイラ拡張機能 (参考: GCCのドキュメント) です。##
が付いていることで、可変長引数が空のときに直前の ,
を削除してくれます。これにより、PANIC("booted!")
のように引数が1つのときにもコンパイルが通るようになります。
マクロの書き方を理解したところで、PANIC
を使ってみましょう。使い方は printf
と同じです。
kernel.cvoid kernel_main(void) { memset(__bss, 0, (size_t) __bss_end - (size_t) __bss); PANIC("booted!"); printf("unreachable here!\n"); }
実際に動かしてみて、正しいファイル名と行番号、そしてPANIC
以後の処理が実行されないこと ("unreachable here!"
が表示されないこと) を確認してください。
$ ./run.sh
PANIC: kernel.c:46: booted!