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; /* * Disable local interrupts. This will prevent panic_smp_self_stop * from deadlocking the first cpu that invokes the panic, since * there is nothing to prevent an interrupt handler (that runs * after setting panic_cpu) from invoking panic() again. */ local_irq_disable();
コメントに書いてある通りで,panic 時には割り込みを禁止状態にして,他の処理は完全は中断されるようになっています.
244: /* * Run any panic handlers, including those that might need to * add information to the kmsg dump output. */ atomic_notifier_call_chain(&panic_notifier_list, 0, buf); /* Call flush even twice. It tries harder with a single online CPU */ printk_safe_flush_on_panic(); kmsg_dump(KMSG_DUMP_PANIC);
その後 ksmg dumpのフック関数をコールして,最後に kmsg_dump をコールしてデバッグ情報をコンソールに出力しています.
287: if (panic_timeout > 0) { /* * Delay timeout seconds before rebooting the machine. * We can't use the "normal" timers since we just panicked. */ 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) { /* * This will not be a clean reboot, with everything * shutting down. But if there is a chance of * rebooting the system it will be rebooted. */ 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: /* * HACK ALERT! This is early. We're enabling the console before * we've done PCI setups etc, and console_init() must be aware of * this. But we do want output early, in case something goes wrong. */ console_init(); if (panic_later) panic("Too many boot %s vars at `%s'", panic_later, panic_param);
これはカーネルのコマンドライン引数(起動時のオプション)を解析した後の処理です.コマンドライン引数が多すぎると"something goes wrong"ということでパニックを起こします
1079: /* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ 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)が指定されていない場合,またはそれが存在しない場合は,どうしようも無いのでパニックを起こします