プロセスとシグナルを扱う
目標
プロセスにシグナルを送り、停止・終了・強制終了がそれぞれ何が違うのかを確認し、ゾンビがどうやって生まれるのかを自分で作ってみます。
なぜ重要なのか
「プロセスが死なない」という報告は、少なくとも4つの異なる状況をひとまとめにした言い方です。アプリケーションが SIGTERM を捕捉して無視したのかもしれず、D 状態でシグナルの処理そのものが後回しになっているのかもしれず、すでに死んでゾンビとして残っているのかもしれません。対応はすべて異なるので、どの階層で止まっているのかを最初に問う必要があります。そして、SIGKILL と SIGSTOP だけは捕捉できないというルールは、カーネルが運用者に残した最後の手段です。だから SIGKILL は後始末の機会を与えず、DB やキューのコンシューマーにとっては電源を引き抜くのと同じです。
ステップ
sleep 3600をバックグラウンドで起動し、その PID を/root/proc/pid.txtに書いてください。- そのプロセスの親 PID を
/root/proc/ppid.txtに書いてください。 - 別の
sleepを起動して停止(SIGSTOP)させ、PID を/root/proc/stopped_pid.txtに、停止後の状態を表す文字1つを/root/proc/state_stopped.txtに書いてください。 - 別の
sleepを起動して PID を/root/proc/killed_pid.txtに書いたあと、SIGTERM で終了させてください。 /root/proc/trapper.shを作ってください。SIGTERM を受け取ったら、/root/proc/term.logにcaught SIGTERMの1行を追加しなければなりません。実行権限を与えてバックグラウンドで起動し、SIGTERM を送ってログがたまることを確認してください。trapper.shを再び起動し、今度は SIGKILL を送ってください。送る直前のterm.logの行数を/root/proc/before_kill.txtに、送った直後の行数を/root/proc/after_kill.txtに書いてください。ラボが終わったとき、trapper.shはひとつも生きていてはいけません。- 子をひとつ生み、その子が先に終わるようにしつつ、親は
waitせずに生き続けるようにしてください。その親の PID を/root/proc/zombie_parent.txtに書いてください。 /root/proc/cleanup.sh <패턴>(パターン)を作ってください。コマンドラインがそのパターンに合うプロセスをすべて終了させ、終了した個数をterminated=<n>の形式で1行出力しなければなりません。自分自身は数えても殺してもいけません。
参考
awk '{print $3}' /proc/<PID>/statで状態を表す文字を見られます。R 実行、S 待機、D 中断不可の待機、T 停止、Z ゾンビ。kill -0 <PID>はシグナルを送らず、存在と権限だけを確認します。生存確認のポーリングの定石です。- よくあるミス1: 3番で終了(TERM)と停止(STOP)を取り違えると、プロセスが消えて採点に失敗します。
- よくあるミス2: 8番で
pgrep -fは自分自身のコマンドラインにもマッチします。自分の PID を除外しないと、スクリプトが自分自身を殺してしまいます。
バックグラウンドプロセスを起動する
sleep 3600 をバックグラウンドで起動し、その PID を /root/proc/pid.txt に書いてください。
コマンドの後ろに & を付けるとバックグラウンドに行きます。直後に $! の中にその PID が入っています。
親 PID を確認する
そのプロセスの親 PID を /root/proc/ppid.txt に書いてください。
/proc//status の PPid の行を見るか、ps -o ppid= -p を使います。
プロセスを停止させる
別の sleep を起動して停止(SIGSTOP)させ、PID を /root/proc/stopped_pid.txt に、停止後の状態を表す文字1つを /root/proc/state_stopped.txt に書いてください。
停止と終了は違います。停止したプロセスは一覧に残り、状態を表す文字が変わります。
丁寧に終了を要求する
別の sleep を起動して PID を /root/proc/killed_pid.txt に書いたあと、SIGTERM で終了させてください。
kill は既定で SIGTERM を送ります。送る前に PID をファイルに書いておいてください。
SIGTERM を捕捉するスクリプト
/root/proc/trapper.sh を作ってください。SIGTERM を受け取ったら、/root/proc/term.log に caught SIGTERM の1行を追加しなければなりません。実行権限を与えてバックグラウンドで起動し、SIGTERM を送ってログがたまることを確認してください。
trap 'コマンド' TERM の形でハンドラを設定します。シグナルを受け取れるように、スクリプトは終了せず生き続けている必要があります。
SIGKILL は捕捉されない
trapper.sh を再び起動し、今度は SIGKILL を送ってください。送る直前の term.log の行数を /root/proc/before_kill.txt に、送った直後の行数を /root/proc/after_kill.txt に書いてください。ラボが終わったとき、trapper.sh はひとつも生きていてはいけません。
trap を設定したそのスクリプトに 9 を送り、ログの行数が増えたかどうかを前後で比べて記録してください。
ゾンビを作ってみる
子をひとつ生み、その子が先に終わるようにしつつ、親は wait せずに生き続けるようにしてください。その親の PID を /root/proc/zombie_parent.txt に書いてください。
子が先に終わり、親が wait しないまま生きていると、ゾンビになります。親が生き続けている必要があります。
パターンに基づく片付けスクリプト
/root/proc/cleanup.sh <패턴>(パターン)を作ってください。コマンドラインがそのパターンに合うプロセスをすべて終了させ、終了した個数を terminated=<n> の形式で1行出力しなければなりません。自分自身は数えても殺してもいけません。
まず pgrep -f で数え、自分自身は除外する必要があります。終了後、個数を terminated= の形式で出力してください。