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

『[なるほどUnixプロセス](https://tatsu-zine.com/books/naruhounix)』が良書だった。
かいつまんでメモ。

<!-- more -->

# 子プロセスのつくり方
```
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](https://www.amazon.co.jp/Working-Sockets-English-Jesse-Storimer-ebook/dp/B00BPYT6PK)"でがっつりやるつもりなので省略する。

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


```
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している。