プロセスの待ち状態は ps
コマンドで確認できるが、これだけだと詳細は分からない。
詳細を知るには /proc/[pid]/stack
が有効。今回はこれについてのメモ。
■ /proc/[pid]/stack とは
[pid] に紐づくプロセスのカーネルスタック内の関数呼び出しのシンボリックトレースを出力する。プロセスが待ち状態の時、具体的に何を待っているのか (何の関数を実行中か) を確認する際に便利。
(※ このファイルは、カーネルビルドオプション「CONFIG_STACKTRACE」が有効 (y) の場合のみに使用可能)
表示例
$ sudo cat /proc/$$/stack [<0>] do_wait+0x1b3/0x220 [<0>] kernel_wait4+0x96/0x120 [<0>] do_syscall_64+0x5b/0xf0 [<0>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
■ 待ち状態のプロセスを確認する
例として、イベントを待つプロセスを起動する。
ここでは、クライアントからの接続 (イベント) を待つサーバを nc コマンド*1で立てる。-l 12345
で nc プロセスが、12345 番ポートで Listen するように実行する。
$ nc -l 12345
次に、起動したプロセスの状態を確認する。
$ ps aux | grep "nc -l" USER PID %CPU %MEM SZ RSS TTY STAT START TIME COMMAND ... user 432678 0.0 0.0 8260 2132 pts/1 S+ 19:59 0:00 nc -l 12345 ...
STAT
列が S+
なので、割り込み可能なスリープであることが分かるが、実際に何を待ち合わせているのかは判断できない。そこで、/proc/[pid]/stack
を使う。
$ sudo cat /proc/$(pidof nc)/stack [<0>] do_select+0x573/0x7a0 [<0>] core_sys_select+0x168/0x310 [<0>] kern_select+0xcd/0x150 [<0>] __x64_sys_select+0x21/0x30 [<0>] do_syscall_64+0x5b/0xf0 [<0>] entry_SYSCALL_64_after_hwframe+0x44/0xa9
select システムコール (sys_select) により、最終的に do_select() で待ち合わせていることが分かる。sys_select() は、多重化された I/O との同期を図る関数である。
ソースコードを確認する
より詳細には、do_select() のソースコードを読む必要がある。厳密には使用環境ごとのディストリビューションのバージョンに沿ったソースコードを確認する必要があるが、ここでは、最新のアップストリームカーネル (v5.9-rc3 時点) で確認する。
まずは、プロセスの状態を遷移させる関数を探す。読んでいくと、★ の部分が目につく。
TASK_INTERRUPTIBLE
とは、割り込み可能なスリープ状態のことなので、
この先で状態を遷移させていると推測できる。
static int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time) { ktime_t expire, *to = NULL; struct poll_wqueues table; ... u64 slack = 0; ... for (;;) { ... if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, ★ to, slack)) timed_out = 1; } ... }
予想どおり、★1 でプロセスの状態を TASK_INTERRUPTIBLE
へ遷移させている。
そして、★3 で実行可能状態 (TASK_RUNNING
) に遷移させている。
つまり、★1 は実行されているが、★3 までは実行されていないということになる。
よって、★2 を読み進めていくことで、具体的に何を待ち合わせているのか、 ★2 へと進む条件は何であるのか、といったことの手掛かりを掴める可能性がある。
static int poll_schedule_timeout(struct poll_wqueues *pwq, int state, ktime_t *expires, unsigned long slack) { int rc = -EINTR; set_current_state(state); ★1 if (!pwq->triggered) rc = schedule_hrtimeout_range(expires, slack, HRTIMER_MODE_ABS); ★2 __set_current_state(TASK_RUNNING); ★3 ... }
以上、このように読み進めていく *2。