2022/06/04
Unix Processes
Descriptors Represent Resources
irb(main):002:0> passwd = File.open('/etc/passwd') => #<File:/etc/passwd> irb(main):003:0> puts passwd.fileno 9 => nil irb(main):004:0> hosts = File.open('/etc/hosts') => #<File:/etc/hosts> irb(main):005:0> puts hosts.fileno 10 => nil irb(main):006:0> passwd.close => nil irb(main):009:0> null = File.open('/dev/null') => #<File:/dev/null> irb(main):010:0> puts null.fileno 9 nil
ファイルディスクリプタは,アクセスしているファイルに番号がつけ,プロセスがアクセスしているリソースを特定する。ファイルが閉じられれば,そのリソースにつけられていた番号は再び使えるようになる。
Standard Streams
STDIN(標準入力),STDOUT(標準出力),STDERR(標準エラー出力)はそれぞれ,1,2,3のファイルディスクリプタの番号がつけられている。
irb(main):006:0> puts STDIN.fileno 0 => nil irb(main):007:0> puts STDOUT.fileno 1 => nil irb(main):008:0> puts STDERR.fileno 2 => nil
Processes Have an Environment
親のプロセスの環境変数は子プロセスにも受け継がれる。Railsだと,RAILS_ENVなど。
Processes Have Exit Code
- exit code 0は成功
- その他のexit codeはエラー
How to Exit a Process
exit
- exit codeを渡せる
exit 22
- at_exitでexit時の処理を渡せる
at_exit { puts 'Last!' } exit
exit!
- status codeは1
- at_exitの処理が走らない
abort
- status codeは1
- 引数で文字列を渡せる
- at_exitの処理が走る
Processes can fork
親プロセスがメインメモリに持つコピー全てが子プロセスに受け継がれる。一つのプロセスを親プロセスとして,二つの子プロセスがforkされる時,3つのプロセスがアプリケーションをロードするよりも高速になる。
forkメソッド
irb(main):001:0> puts "parent process pid is #{Process.pid}" parent process pid is 21210 => nil irb(main):002:1* if fork irb(main):003:1* puts "entered the if block from #{Process.pid}" irb(main):004:1* else irb(main):005:1* puts "entered the else block from #{Process.pid}" irb(main):006:0> end entered the if block from 21210 => nil entered the else block from 21223 => nil
ifにおけるfork
は親プロセスからforkして作られた子プロセスのpidが返されるため,if内の処理が走り,その次のfork
は子プロセスのfork
となり,nilが返るためelse内の処理が走る。
irb(main):001:0> puts Process.pid 21257 irb(main):002:1* fork do irb(main):003:1* puts Process.pid irb(main):004:1* puts Process.ppid irb(main):005:0> end => 21291 21291 21257
fork
メソッドのブロック内は,子プロセス内の処理が行われる。
Orphaned Processes
fork do 5.times do sleep 1 puts "I'm an orphan!" end end abort "Parent process died..."
上記のコードの出力結果は以下となる。
irb(main):001:1* fork do irb(main):002:2* 5.times do irb(main):003:2* sleep 1 irb(main):004:2* puts "I'm an orphan!" irb(main):005:1* end irb(main):006:0> end => 21575 irb(main):007:0> abort "a" a ~ % I'm an orphan! # 親プロセスはexitし,ターミナル上の出力 I'm an orphan! I'm an orphan! I'm an orphan! I'm an orphan!
abort "a"
で,fork
内の子プロセスの親プロセスは終了するが,子プロセスの処理は中止されず,irb
の親プロセスのターミナル上でfork
内の子プロセスの処理が継続する。
Being CoW Friendly
copy-on-write
親プロセスをforkして子プロセスを作成した瞬間に親プロセスのデータが全てコピーされるわけではなく,子プロセスのデータに変更を加えてようとしたタイミングで始めて親プロセスのデータのコピーが行われる。
irb(main):015:0> arr = [1,2,3] => [1, 2, 3] irb(main):016:1* fork do irb(main):017:1* p arr # 親プロセスのコピーは起こっていない irb(main):018:1* arr << 4 # 子プロセスのデータに変更を加えようとして始めて親プロセスのデータがコピーされる irb(main):019:1* p arr irb(main):020:0> end => 21690 [1, 2, 3] [1, 2, 3, 4] irb(main):021:0> p arr [1, 2, 3] => [1, 2, 3]
上記のアルゴリズムは,mark-and-sweepアルゴリズムを使用している。
Mark and Sweep Algorithm
Mark and Sweep Algorithmとは
Garbage Collectionのアルゴリズム。Garbage Collectionは,参照されていないメモリを見つけ,解放することを行う。
手順
- メモリに,デフォルトで0(false)のマークをつける。そして,参照できるメモリには深さ優先探索により1(true)のマークを付ける。
- 参照されていないメモリ領域(falseマークのついている領域)を解放し,参照されているメモリ領域のマークをtrueからfalseに変更する。
メリット
- 循環参照の場合でも無限ループにならない
- アルゴリズムの処理中に余分なオーバヘッドが発生しない
デメリット
- Garbage Collectionの処理が実行中の時,メインプログラムが中断される
- Mark and Sweepアルゴリズムが複数回実行された後,使用されているメモリ空間が多くの小さな領域に分割されてしまう(フラグメンテーション)