Linux カーネルのソースコードを読んだのでメモを公開します.Linuxカーネルを読む際の参考になれば幸いです.
ユーザ空間のプログラムは,NULLポインタアクセスなどで例外をおこすと,例外ハンドラが起動します.例外ハンドラは,プログラムをコアダンプさせたり,強制終了させたりします.
同じようにカーネル空間で例外が発生すると,例外ハンドラが起動します.復旧不可能な致命的なエラーの場合は,カーネルはデバック情報をコンソールに出力して,動作を停止させます.これがカーネルパニックです.
デバッグなどの目的で意図的にカーネルパニックを発生させることもできます.sysreqの機能を使います.つまりroot権限で,コマンドラインで
$ echo c > /proc/sysrq-trigger
を実行します
キーボードで sysreq + c を押しても同じことができます.つまり[Alt]+[Print Screen]+[c]を同時に押します
Webサーバーなどで不運にもカーネルパニックが発生した場合は,とりあえずサーバーを自動再起動させるという運用方法が考えられます.
(運用ポリシーとして,障害時はサーバーを止めたままにするか,自動再起動させるか,どちらが良いかは意見が分かれるところです)
この設定はカーネルの起動オプションや,sysctl で設定できます
以下の内容で /etc/sysctl.d/99-panic.conf みたいなファイルを作成するだけです
kernel.panic = 90
この場合カーネルパニックから90秒後にシステムは自動再起動します
デバイスドライバ開発時は,kernel.panic = 10ぐらいにしておいて,再起動した場合は /var/log 以下のログを眺める,という使い方も出来ます
カーネルパニックの処理は,ソースコードとしては kernel/panic.c あたりで実装されています
kernel.panic などのオプション設定の定義は kernel/panic.c の687行目あたりにあります
687:
core_param(panic, panic_timeout, int, 0644);
core_param(panic_print, panic_print, ulong, 0644);
core_param(pause_on_oops, pause_on_oops, int, 0644);
core_param(panic_on_warn, panic_on_warn, int, 0644);
kernel.panic = 90 と設定した場合は panic_timeout というint型の変数に90という値が格納されます.
panic_timeout 変数の定義は46行目
int panic_timeout = CONFIG_PANIC_TIMEOUT;
EXPORT_SYMBOL_GPL(panic_timeout);
です.このように 初期値は CONFIG_PANIC_TIMEOUT つまり カーネルのconfigで設定しています
念の為 CONFIG_PANIC_TIMEOUT の値を確認します. 設定ファイルは .config なので grep するだけです
$ cd path/to/カーネルのソースコードのディレクトリ
$ grep CONFIG_PANIC_TIMEOUT .config
CONFIG_PANIC_TIMEOUT=60
私のカーネルは60秒でビルドされていました
パニック時の処理
panic(const char *fmt, ...) という関数で実装されています
161:
void panic(const char *fmt, ...) {
static char buf[1024];
va_list args;
long i, i_next = 0, len;
int state = 0;
int old_cpu, this_cpu;
bool _crash_kexec_post_notifiers = crash_kexec_post_notifiers;
local_irq_disable();
コメントに書いてある通りで,panic 時には割り込みを禁止状態にして,他の処理は完全は中断されるようになっています.
244:
atomic_notifier_call_chain(&panic_notifier_list, 0, buf);
printk_safe_flush_on_panic();
kmsg_dump(KMSG_DUMP_PANIC);
その後 ksmg dumpのフック関数をコールして,最後に kmsg_dump をコールしてデバッグ情報をコンソールに出力しています.
287:
if (panic_timeout > 0) {
pr_emerg("Rebooting in %d seconds..\n", panic_timeout);
for (i = 0; i < panic_timeout * 1000; i += PANIC_TIMER_STEP) {
touch_nmi_watchdog();
if (i >= i_next) {
i += panic_blink(state ^= 1);
i_next = i + 3600 / PANIC_BLINK_SPD;
}
mdelay(PANIC_TIMER_STEP);
}
}
if (panic_timeout != 0) {
emergency_restart();
}
panic_timeout > 0 の場合は,PANIC_TIMER_STEP ミリ秒刻みでウエイトを入れながら panic_blinck()を繰り返しコールしています.おそらく画面の点滅表示処理です
#define PANIC_TIMER_STEP 100
PANIC_TIMER_STEPは100ミリ秒でした
そして panic_timeout秒経過したら emergency_restart() をコールして強制終了をおこないます
panic()はどこから呼ばれているか?
カーネルのソースコードを "panic(" で検索すると直ぐに判りますが,至るところで panic() をコールするようになっています.
例えばカーネル起動時の処理 init/main.c では3箇所 panic() を呼び出す処理が書かれています
662:
console_init();
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
これはカーネルのコマンドライン引数(起動時のオプション)を解析した後の処理です.コマンドライン引数が多すぎると"something goes wrong"ということでパニックを起こします
1079:
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");
これはカーネルの初期化が終了して,システムのinit プロセス(PIDが0のプロセス)を起動する部分です.
initプロセスの実行ファイル(/bin/systemd とか /bin/sh)が指定されていない場合,またはそれが存在しない場合は,どうしようも無いのでパニックを起こします