『なるほどUNIXプロセス』読書メモ

tatsu-zine.com

自分なりに整理したもの、理解しづらかったので追加で調べて理解したものなどメモです。

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.waitProcess.waitpidは同じ関数なので、Process.waitで引数に子プロセスを指定すればProcess.waitpidと同じ動きをするが、そのときやりたいことによってメソッドを変えるのが可読性の点で良い。
  • 感想
    • 同じ関数にする必要あったのかな?と感じた。waitは引数取れない、waitpidは引数必須とかだと問題あるのかなあ。

ゾンビプロセス

  • カーネルは終了したプロセスの情報をキューに持っている
  • Process.waitなどされない限り、プロセス情報を破棄できない(ゾンビプロセス)
  • リソースが無駄にならないよう、Process.detachを使うと、プロセスの終了を監視するスレッドを生成することができる。(親プロセスはブロックされない)

docs.ruby-lang.org

  • detachはpidを引数に取る必要があるが、trap(:CHLD)で子プロセスが親プロセスに送信するSIGCHLDを見て、処理を書くことができる。
    • ただし、シグナルはいつ受診するかわからないので、Process.waitする時点ですでに子プロセスがなく、Errno::ECHILDの例外を出すことも。そのため、この例外のrescueをしておくべし。
  • chatGPTに聞いてみた。detachtrap(: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 methodcall' 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でやっていること
  • ①で作成した子プロセス(③の子プロセスの親プロセス)を終了する
  • ここでできた子プロセスはセッションリーダーでないので、端末制御ができず、完全に端末と分離された状態になる。