Hibariya

プロセスをforkするときのこと

孤児プロセスとゾンビプロセスの違いがうまく理解できてなかったけど、ようやく違いを確認することができた。

孤児プロセス

孤児プロセスは、親プロセスがwaitせずに先に逝ってしまった後も走り続けている子プロセス。 Orphan Process とも呼ばれる。 親のいなくなった子プロセスはinitプロセスの子(孤児)になる。

親プロセスが死んで、子プロセスの親が変化する様子を見るには以下のようなスクリプトを実行したあとでファイルをtailすると分かりやすそう。

Process.fork do
  File.open('orphan', 'a') do |f|
    loop do
      sleep 1

      # 親プロセスのpidをファイルに書き出す
      f.puts Process.ppid
      f.flush
    end
  end
end

sleep 5

このプログラムを実行して、orphantail -fで観察していると、5秒後に親プロセスが死んで、子プロセスの親プロセスがinitに変化していることを確認できる。

$ tailf orphan
80361 # まだ親プロセスの子
80361
80361
80361
80361
1     # ここでinitの養子になってる
1
1
1

initは孤児プロセスをwaitしてくれる。

ゾンビプロセス

ゾンビプロセスは、既に処理を終えて死んだ子プロセスが、まだ生きている親プロセスにwaitされるのを待っている状態のこと。

子プロセスが死んでも親プロセスが生きている限り、いつ子プロセスの終了ステータスが参照されても(waitされても)いいように、その情報はプロセステーブルのエントリとして残る。 プロセスとしては死んでいるけれど、waitされるかもしれないのでプロセステーブルに残っている状態がゾンビ。

puts Process.fork { sleep 1 }

sleep 10

上のプログラムを実行して、表示されたpidを元に、別の端末でプロセスの状態を確認すると、ゾンビ状態になっていることがわかる(10秒後には親プロセスが死ぬのでゾンビも消える)。

$ ps -ho pid,state 81793
  PID STAT
  82077 Z+

ゾンビプロセスの発生を防ぐには、親プロセスで確実にwaitする必要がある。 RubyならProcess.waitpidを使うといい。 waitすると子プロセスが終了するまで親プロセスの処理がブロックされる。

pid = Process.fork { sleep 1 }

Process.waitpid pid

もしくは、Double Forkを行うと親プロセスで待たなくてもよくなる。 生成した子プロセスに孫プロセスを生成させ、さらに子プロセスは即座に終了させることで、孫プロセスがinitの子プロセス(孤児プロセス)となってwaitはinitに任せることができるようになる。

pid = Process.fork {
  puts Process.fork {
    sleep 20 # 何かしらの処理
  }

  sleep 1
}

Process.waitpid pid

まとめ