Engineering from Scratch

エンジニア目指してます

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は,参照されていないメモリを見つけ,解放することを行う。

手順

  1. メモリに,デフォルトで0(false)のマークをつける。そして,参照できるメモリには深さ優先探索により1(true)のマークを付ける。
  2. 参照されていないメモリ領域(falseマークのついている領域)を解放し,参照されているメモリ領域のマークをtrueからfalseに変更する。

メリット

  • 循環参照の場合でも無限ループにならない
  • アルゴリズムの処理中に余分なオーバヘッドが発生しない

デメリット

  • Garbage Collectionの処理が実行中の時,メインプログラムが中断される
  • Mark and Sweepアルゴリズムが複数回実行された後,使用されているメモリ空間が多くの小さな領域に分割されてしまう(フラグメンテーション

参考

www.geeksforgeeks.org

参考

workingwithruby.com