自分なりに整理したもの、理解しづらかったので追加で調べて理解したものなどメモです。
forkとCoW(Copy on Write)
fork: とあるプロセスから子プロセスを作ること。親プロセスがメモリに持つ情報をすべて持つ。
コピーをするためのオーバーヘッドが問題になってしまうことを避けるために、CoWの仕組みで対応
- fork時にメモリ情報をコピーするのではなく、まずは親と同じメモリを共有。子プロセス側で情報の変更が必要になった場合(配列の中身を変えるなど)に、初めてコピーする。親プロセスのメモリの情報は変えない。
子プロセスを作った親プロセスが死んでも、カーネルは子プロセスを特別視しないので、子プロセスには何も起きない(=一緒に死なない)
- こうなったプロセスを孤児プロセスという
forkは2度返る
- 親プロセスに対しては子プロセスのidを返し、子プロセスからはnilが返る
- これを活用するしたコードが、
exit if fork
。- 親プロセスを終了させ、子プロセスを孤児プロセスとして継続させる。
Process.wait2
子プロセスのどれか1つの終了を待つ、という役割は同じだが、pidのみ返すのがpidで、pidと終了ステータスを返すのがwait2。
また、子プロセスを指定して終了を待つには
Process.waitpid
を使う。Process.wait
とProcess.waitpid
は同じ関数なので、Process.wait
で引数に子プロセスを指定すればProcess.waitpid
と同じ動きをするが、そのときやりたいことによってメソッドを変えるのが可読性の点で良い。- 感想
- 同じ関数にする必要あったのかな?と感じた。waitは引数取れない、waitpidは引数必須とかだと問題あるのかなあ。
ゾンビプロセス
- カーネルは終了したプロセスの情報をキューに持っている
- Process.waitなどされない限り、プロセス情報を破棄できない(ゾンビプロセス)
- リソースが無駄にならないよう、
Process.detach
を使うと、プロセスの終了を監視するスレッドを生成することができる。(親プロセスはブロックされない)
detach
はpidを引数に取る必要があるが、trap(:CHLD)
で子プロセスが親プロセスに送信するSIGCHLDを見て、処理を書くことができる。- ただし、シグナルはいつ受診するかわからないので、Process.waitする時点ですでに子プロセスがなく、
Errno::ECHILD
の例外を出すことも。そのため、この例外のrescue
をしておくべし。
- ただし、シグナルはいつ受診するかわからないので、Process.waitする時点ですでに子プロセスがなく、
- chatGPTに聞いてみた。
detach
とtrap(:CHLD)
の使い分けは?Process.detach
は 1つの子プロセスを個別に管理するのに適している。trap(:CHLD)
は すべての子プロセスを一括管理するのに適している。forkしまくる場合など。
trap
trap(:INT) { puts 'first' } old = trap(:INT) { old.call puts 'second' exit } sleep 5
↑これだとエラーが起きずに、
system_handler = trap(:INT) { puts 'abort to exit!' system_handler.call } sleep 5
↑これだとエラーが起きる(undefined method
call' for "DEFAULT":String (NoMethodError)`)理由が最初よくわからなかった。
- エラーが出ないコードは、最初に
trap(:INT)
が設定されていて、old.call
ではそれを呼んでいる。 - エラーが出るコードは、他に
trap(:INT)
をしていないので、system_hander.call
でデフォルトのアクションを呼ぼうとし、失敗。 - これが、書籍の中の「システムのデフォルトの振る舞いは保存できない」ということと理解した。
- その他メモ
- プロセスはいつでもシグナルを受信できる
デーモンプロセス
exit if fork Process.setsid exit if fork
このコードの2回目のexit if fork
をする理由がよくわからなかったので調べながら以下の理解をした。
①1回目のexit if fork
でやっていること
- 親プロセスを終了して孤児プロセスを作っている
- プロセスグループ(セッション)は親から引き継いでいる状態
②Process.setsid
でやっていること
- ①で作成した子プロセスをプロセスグループリーダー(セッションリーダー)にする
- 親プロセスから切り離すことができるが、セッションリーダーは端末制御をしようと思えばできる
③1回目のexit if fork
でやっていること
- ①で作成した子プロセス(③の子プロセスの親プロセス)を終了する
- ここでできた子プロセスはセッションリーダーでないので、端末制御ができず、完全に端末と分離された状態になる。