プロセスグループ・セッションについて勉強した

fabricというデプロイツールでサーバーを動作させるコマンドが上手く実行出来なくて、原因を調べるために勉強したメモ。(原因はまだよくわかってない)

プロセスグループ

名前の通り、プロセスの集合のこと。
あるプロセスからforkした子プロセスはデフォルトでは親プロセスと同じプロセスグループ。
また、下のようにパイプでつないだプロセス同士は同じプロセスグループになる。

$ command1 | command2 | command3

プロセスグループにはリーダーとなるプロセスがいて、プロセスグループリーダーという。
プロセスグループにはプロセスのようにidがあり、プロセスグループIDという。
プロセスグループIDはそのプロセスグループのリーダーのプロセスIDと同じ値を持つ。

プロセスグループは端末によるジョブ制御の基本的な単位となる。
つまりジョブ=プロセスグループ。

$ seq 100000000 | sort | uniq

を実行し、実行中に ^C(Control + c)を押すと、コマンドの実行が終了する。
^Cを押すと端末の機能により現在フォアグラウンドで実行中のジョブ(プロセスグループ)にSIGINTというシグナルが送信され、実行が終了させられる。

一方、killコマンドを使ってseqを実行しているプロセスを殺すとどうなるか。

$ seq 10000000 | sort | uniq &

とバックグラウンドで実行して、プロセスの情報を調べる。(コマンドは実行時間を稼げるコマンドを適当に並べただけ)

$ ps -o pid,pgid,sid,args
  PID  PGID   SID COMMAND
31089 31089 31089 /bin/bash
31580 31580 31089 seq 10000000
31581 31580 31089 sort
31582 31580 31089 uniq
31583 31583 31089 ps -o pid,pgid,sid,args

"-o"オプションで出力フォーマットを指定している。
パイプでつながれた3つのプロセスが同じプロセスグループ(そして同じセッション)に属してることがわかる。

ここで、seqを実行しているプロセスを殺してみる。

$ kill -2 31580

すると、しばらく経ってから標準出力に数字がいっぱい出力されるはずだ。

つまり、killでプロセスを殺した場合パイプでつながれた他のプロセスは生きたままだということだ。
これはkillコマンドの正しい動作であるけれど、端末のユーザーとしては不便極まりない。

ジョブ制御

先ほど唐突に出てきた、端末におけるジョブ制御について簡単にまとめておく。
ジョブ制御は一つの端末で複数のコマンド(ジョブ)を実行できるようにするもの。

$ command &

bashでコマンドの後ろに&を付けて実行すると、bashの機能により実行はバックグラウンドで行われ、そのコマンドの実行が終了していなくても他のコマンドを実行できるようになる。
このようにバックグラウンドで実行するジョブをバックグラウンドジョブという。
コマンドを普通に実行した場合、それはフォアグラウンドジョブとなり、実行終了まで待たされる。
端末は一つのフォアグラウンドジョブと複数のバックグラウンドジョブを持つことができる。

ジョブの状態にはフォアグラウンドで実行中、バックグラウンドで実行中以外に中断という状態がある。
フォアグラウンドでコマンドを実行中に ^Z(Control+z)を押すと実行が中断される。

$ seq 1000000000000 | sort
^Z
[1]+  Stopped                 seq 1000000000000 | sort

もう一度フォアグラウンドで実行したかったらfgと入力すればよい。
中断した状態でバックグラウンドで実行したかったらbgと入力すればよい。
バックグラウンドで実行中のジョブを中断させたかったらstopと入力すればよい。(なぜか僕の環境ではstopコマンドがないと怒られた)

セッション

セッションとはプロセスグループの集合である。
セッションにもセッションリーダーとなるプロセスがいる。
セッションのIDはセッションリーダーのプロセスIDと同じになる。
セッションは一つの制御端末を持つことが出来て、制御端末を持つセッションのセッションリーダーを制御プロセスという。 sshでログインして、インタラクティブなシェルを実行している場合、セッションリーダーはシェルである。

制御プロセスと制御端末の接続が切断されたら、カーネルは制御プロセスとフォアグラウンドプロセスジョブ内の全てのプロセスにシグナルSIGHUPを送信する。
bashが制御プロセスだった場合、bashはSIGHUPを受け取ると、exitする前に端末内の全ジョブにSIGHUPを送信する(実行中断中のジョブにはSIGCONTを送って実行を再開させてからSIGHUPを送る)。
SIGHUPを受け取ったときのデフォルトの動作は停止なのでプロセスは死ぬ。

sshが切れたときとかに実行中のプロセスが死んでしまうのはこのあたりのことが起こっている。
nohupコマンドはSIGHUPを無視するようにしてくれる。

色々と理解があやふや(特に端末)なので、所々間違っているかもしれない。