LoginSignup
2
0

More than 3 years have passed since last update.

『なるほどUnixプロセス』を読んだ

Posted at

なるほどUnixプロセス』が良書だった。
かいつまんでメモ。

子プロセスのつくり方

p Process.pid  # => 31235

# 単にforkと書くと Kernel.#fork が呼ばれる。
fork { p Process.pid }  # => 31248

# Process.#fork も Kernel.#fork と同じ。
Process.fork { p Process.pid }  # => 31249

#  バッククォートも子プロセスをつくっている
p `echo $$`  # => "31250\n"

子プロセスは親プロセスのメモリのコピーを引き継ぐ

season = 'summer'

fork do
  p season  # => 'summer'
  season.upcase!
  p season  # => 'SUMMER'
  animal = 'giraffe'
end

Process.wait
p season  # => 'summer'
p animal  # => undefined local variable or method `animal'

環境変数やファイルディスクリプタも同様。

標準入出力とは

  • 入力元/出力先を指定しない場合にデフォルトで入力元/出力先となるところ。
  • Rubyでは例えば、標準入力はKernel.#getsの入力元に、標準出力はKernel.#putsの出力先になる。
  • $stdin, $stdoutという変数によって表現される。
  • $stdinのデフォルト値はSTDINという定数であり、これはキーボードからの入力を表すIOオブジェクト。
  • $stdoutのデフォルト値はSTDOUTという定数であり、これは画面への出力を表すIOオブジェクト。
$stdin   # => #<IO:<STDIN>>
$stdout  # => #<IO:<STDOUT>>

STDIN.class   #=> IO
STDOUT.class  #=> IO

標準入力を変更してみる。

% echo hoge > in.txt
% ruby -e '$stdin = File.open("in.txt"); puts gets'
=> hoge

標準出力を変更してみる。

% ruby -e '$stdout = File.open("out.txt", "w"); puts "fuga"'
% cat out.txt
=> fuga

ゾンビプロセス

  • psでみれる情報を「プロセステーブル」と呼ぶ。

  • プロセスの実行が終了した状態で、以下のいずれかが起こるとプロセステーブルから削除される。

    • 親プロセスでwaitしてもらう(「終了したよ」という報告を親プロセスに受け取ってもらう)
    • 親プロセスの実行が終了する

このいずれもが起こらずに、「実行終了したけれどプロセステーブルには残っている」プロセスを、ゾンビプロセスと呼ぶ。

実際に確認しよう。

親プロセスのwaitによってプロセステーブルから削除される場合

以下のスクリプトを用意する。

$PROGRAM_NAME = 'ruby_parent'

fork do
  $PROGRAM_NAME = 'ruby_child'
  sleep(5)
end

Process.wait
sleep(10)

ruby_childの実行は約5秒で終了し、ruby_parentはさらに約5秒たってから実行終了するはずだ。

スクリプト実行直後
% ps u | grep ruby
USER      PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
takashi 31931   0.0  0.1  4300776   7184 s001  S+   10:58AM   0:00.13 ruby_parent
takashi 31944   0.0  0.0  4300520    836 s001  S+   10:58AM   0:00.00 ruby_child

どちらもステータスは"S+"でスリープ状態になっている。

5秒経過時点
% ps u | grep ruby
USER      PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
takashi 31931   0.0  0.1  4300776   7188 s001  S+   10:58AM   0:00.13 ruby_parent

ruby_childは、ruby_parentにwaitされたことによってプロセステーブルから削除された。

10秒経過時点
% ps u
(出力なし)

ruby_parentは、さらにその親プロセスであるシェルにwaitされたことによってプロセステーブルから削除されたのだと思う(確信がない)。

親プロセスの終了によってプロセステーブルから削除される場合

先程のスクリプトからProcess.waitを削除する。

$PROGRAM_NAME = 'ruby_parent'

fork do
  $PROGRAM_NAME = 'ruby_child'
  sleep(5)
end

sleep(10)
スクリプト実行直後
% ps u | grep ruby
USER      PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
takashi 31880   0.0  0.0  4299496    832 s001  S+   10:32AM   0:00.00 ruby_child
takashi 31867   0.0  0.1  4299752   7248 s001  S+   10:32AM   0:00.07 ruby_parent

どちらもステータスは"S+"でスリープ状態になっている。

5秒経過時点
% ps u | grep ruby
USER      PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
takashi 31867   0.0  0.1  4299752   7248 s001  S+   10:32AM   0:00.07 ruby_parent
takashi 31880   0.0  0.0        0      0 s001  Z+   10:32AM   0:00.00 (ruby)

ruby_childはステータスが"Z+"でゾンビプロセスになっている。(プロセス名の表示は"ruby_child"から"(ruby)"になっている。)

10秒経過時点
% ps u
(出力なし)
  • ruby_childは、ruby_parentの実行が終了したことによってプロセステーブルから削除された。
  • ruby_parentは、さらにその親プロセスであるシェルにwaitされたことによってプロセステーブルから削除されたのだと思う。

この例では一時的にゾンビプロセスが生じたものの、親プロセスがすぐに終了したため、ゾンビプロセスも削除することができた。

しかし親プロセスがデーモンとして存続する場合、ゾンビプロセスは半永久的に残ってしまう。リソースを握ったまま解放してくれないと悲しい気持ちになる。

孤児プロセス

孤児プロセスとは、自身を実行している途中に親プロセスが実行終了したプロセス。

本来の親プロセスを失った時点で、孤児プロセスはinitプロセスの子プロセスへと移行する。

実際にみてみる。

$PROGRAM_NAME = 'ruby_parent'

fork do
  $PROGRAM_NAME = 'ruby_child'
  p Process.ppid
  sleep(10)
  p Process.ppid
  loop { sleep(1) }
end

sleep(5)

ruby_parentは5秒で終了し、ruby_childは無限ループにより存続する。

スクリプト実行直後
% ps u | grep ruby
USER      PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
takashi 32018   0.0  0.1  4284392   7184 s001  S+   11:29AM   0:00.13 ruby_parent
takashi 32031   0.0  0.0  4284136   1080 s001  S+   11:29AM   0:00.00 ruby_child
  • どちらもステータスは"S+"でスリープ状態になっている。
  • この時点でのp Process.ppidの結果として32018が出力された。これは確かにruby_parentのpidである。
5秒経過時点
% ps u | grep ruby
USER      PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
takashi 32031   0.0  0.0  4284136   1080 s001  S    11:29AM   0:00.00 ruby_child

ruby_parentは実行終了し、さらにその親プロセスであるシェルにwaitされたことによってプロセステーブルから削除されたのだと思う。

10秒経過時点
% ps u | grep ruby
takashi 32031   0.0  0.0  4284136   1080 s001  S    11:29AM   0:00.00 ruby_child

この時点でのp Process.ppidの結果として1が出力された。これはinitプロセスのpidであり、本来の親プロセスであるruby_parentを失ったためにinitプロセスの子プロセスとなったことが分かる。

この例では、ruby_childがデーモン化して存続することとなった。一般に、デーモンプロセスとは意図的につくられた孤児プロセスである。

IPC(Inter-Process Communication)

主にパイプとソケットという2つの方法がある。

ソケットは"Working with TCP sockets"でがっつりやるつもりなので省略する。

以下はパイプのサンプル。

reader, writer = IO.pipe

fork do
  reader.close
  writer.puts 'message from child process'
end

writer.close
puts reader.read  # => message from child process

単方向の通信であることに注意する。

親プロセスでIOオブジェクトのペアをつくって、それらを子プロセスにコピーするので4つのオブジェクトができるが、そのうち2つは不要。
writeする方のプロセスではreaderをcloseし、readする方のプロセスではwriterをcloseしている。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0