こんにちは、UUUMでエンジニアやっておりますオオハシと申します。
今までプロセスについて書籍やネットの記事の流し読みでなんとなくわかったつもりになっていたのですが
なるほどUnixプロセス - Rubyで学ぶUnixの基礎という書籍では
普段業務で取り扱っているrubyを用いてプロセスを説明されているのが特徴で
プロセスを理解する上での大きな助けとなりました。
以下、学習したことを大まかにまとめました。
全てのプロセスはID(pid)を持っている
p Process.pid
スクリプトファイルの実行やirb、各種shell、バックグラウンドプロセスなど
プログラムの実行毎に異なる値が割り振られる。
forkして子プロセスを作ってみる
fork p Process.pid
出力される2つのpidの値が異なる
つまり2つの異なるプログラムが並列して実行されている。
親プロセスと子プロセスで処理を分けたい
p fork
親は子のプロセスIDを、子はnilを返り値にとるので
以下のように書ける
if fork p "親のプロセスID:#{Process.pid}" else p "子のプロセスID:#{Process.pid}" end
子プロセスの処理内容を指定したい
ブロックを渡す
fork do p "子プロセスのみ評価される" end
孤児プロセスとは
親が先に終了してしまった子プロセス
fork do p "子プロセス開始" 5.times do |i| sleep 1 puts "#{i}秒経過" end p "子プロセス終了" end abort "親プロセス終了"
親プロセス終了後も子プロセスの処理が続行し、端末から<Ctrl-c>などで停止も出来ない
何故か?
→子プロセスは親プロセスを通じて間接的に端末制御されるため。
したがって親プロセスが先に終了すると子プロセスを端末による制御ができなくなる。
どうやって管理するのか?
子プロセスの終了を待つ
fork do p "子プロセス開始" 5.times do |i| sleep 1 puts "#{i}秒経過" end p "子プロセス終了" end Process.wait # 子が終了するまで親の処理が止まる。親が生きてるため端末制御も可能。 abort "親プロセス終了"
シグナルを送信する
例えば別のプロセスから孤児となったプロセスのpidを指定してシグナルを送ることでプロセス制御が可能。
fork do p "別プロセスから kill -INT #{Process.pid} を実行してください" p "子プロセス開始" 5.times do |i| puts "#{i * 5}秒経過" sleep 5 end p "子プロセス終了" end abort "親プロセス終了"
主要なシグナル一覧
ゾンビプロセスとは
カーネルは終了した子プロセスの情報をキューに格納する。
Process.wait
して子プロセスの終了ステータスを取得されないと
終了したはずの子プロセスの情報が取り除かれず、ゾンビプロセスとして取り扱われる。
ゾンビプロセス確認の仕方
pid = fork { sleep 1 } puts "ps -ho pid,state -p #{pid} を実行してください" sleep 10
プロセス間で通信する
パイプ(単方向通信)
reader, writer = IO.pipe # not opened for writing (IOError) reader.write("読み込む側からは書き込み出来ない")
親・子のプロセス間通信をする
reader, writer = IO.pipe fork do reader.close 10.times do |i| # 子プロセスから書き込む writer.puts "#{i+1}回目の書き込み" end end writer.close # 親プロセスで読み込む while message = reader.gets $stdout.puts message end
ソケット(双方向通信)
使用例
require 'socket' child_socket, parent_socket = Socket.pair(:UNIX, :DGRAM, 0) maxlen = 1000 fork do parent_socket.close 2.times do |i| # parent_socket.sendされるまで待つ instruction = child_socket.recv(maxlen) child_socket.send("#{instruction} -> parent #{i+1}回目", 0) end end child_socket.close 2.times do parent_socket.send("parent -> child", 0) end 2.times do # child_socket.sendされるまで待つ $stdout.puts parent_socket.recv(maxlen) end
プロセスグループ・セッションについて
セッションは同一セッションIDを持つプロセスグループを束ねる
プロセスグループは同一グループIDを持つプロセスを束ねる
セッション内でセッションリーダーと呼ばれるひとつのプロセスが制御端末と接続し
セッション内全プロセスに端末から受信したシグナルを伝搬する。
終わりに
本書で解説されていたプロセスに関するごく一部を紹介してきました。
この他デーモンプロセスやシグナルの捕捉、rackその他Rescue, Unicornのプロセス管理など
プロセスに関する事項がrubyによって読みやすい形でまとめられています。
UNIXの基礎であるプロセスの知見を深めておくことは、プログラミング言語を問わず通用する技術であり
今後のエンジニアライフをより良くする上でも大変有用であると考えています。
プロセスについて理解を深めたい方は是非著書を手にとってご覧いただければと思います。