銀行員からのRailsエンジニア

銀行員からのRailsエンジニア

銀行員から転身したサービス作りが大好きなRailsエンジニアのブログです。個人で開発したサービスをいくつか運営しており、今も新しいサービスを開発しています。転職して日々感じていること、個人開発サービス運営のことなどを等身大で書いていきます。

【技術書メモ】なるほどUnixプロセス Rubyで学ぶUnixの基礎 〜毎週アウトプットチャレンジ⑤〜

毎週 1冊技術書を読んでブログでアウトプットするチャレンジの第5弾ですーー!
なんとか続いています、、、!習慣になってきた、、、!
 
Unixの基礎であるプロセスについてRubyで解説している本です。
 
読む前は、Unix?プロセス?? というお恥ずかしい状態だったのですが、本書を2周読み終えた今、少しずつ理解できてきました。
 
少しとっつきにくい内容かもしれませんが、知っていて損ではない内容だと思うので是非見てみてください。

f:id:ysk_pro:20180819120359j:plain

第3章 プロセスにはIDがある

  • システムで動作するすべてのプロセスは固有の識別子(プロセスID)があり、pidと呼ばれている
  • pidはプロセスにまつわる情報は何も持たずに、単に連番になっている数値のラベルである
  • pidはログファイルで有効に使われている。pidにより、ログファイルの各行がどのプロセスから出力されたものかを識別できる
 

第4章 プロセスには親がいる

  • 親プロセスはそのプロセスを起動したプロセスである
 

第5章 プロセスにはファイルディスクリプタがある

  • Unixの世界ではすべてがファイルであり、デバイスはファイルとして扱われる
  • 実行中のプロセスでリソースを開くと、ファイルディスクリプタ番号が割り当てられる
 

第6章 プロセスにはリソースの制限がある

  • カーネルによって1プロセスごとにリソースの制限が設定されている
  • 制限は設定を変更することでほぼ無限大にすることができる
 

第7章 プロセスには環境がある

  • 環境とは環境変数のことをいう
  • 環境変数はキーとバリューが対になっており、プロセスで使えるデータを保持している
  • すべてのプロセスは親プロセスから環境変数を引き継ぐ
 

第8章 プロセスには定数がある

  • RubyプロセスはARGVという特別な配列を参照できる
  • argvとは引数の配列を意味するargument vectorの略である
  • argvにはコマンドラインからプロセスに渡された引数が格納されている
  • プログラムにファイル名を渡したいケースにARGVが最もよく使われる
 

第9章 プロセスには名前がある

  • Unixプロセスにはプロセスの状態を知らせるための手段がほとんどなく、これを回避するためにログファイルを作成した
  • プロセスのレベルで情報を伝えるための仕組みは二つあり、一つはプロセス名でもう一つは終了コードである
 

第10章 プロセスには終了コードがある

  • 慣習としてプロセスは正常終了時には終了コード0を返す。それ以外の終了コードはエラーを示しており、終了コードごとに異なるエラーの種類を表す
  • raiseで送出された例外が捕捉されない場合もプロセスは終了する
 

第11章 プロセスは子プロセスを作れる

  • fork(2)で子プロセスを生成すると、実行中のプロセスの完全なコピーを生成でき、親プロセスで使われている全てのメモリのコピーを受け継ぐ
 

第12章 孤児プロセス

  • 親プロセスが死んでも子プロセスは生き続ける
  • デーモンプロセスとは意図的に孤児化されたプロセスであり、いつまでも動き続けることを狙いとしている
 

第13章 プロセスは優しい

  • fork(2)は親プロセスの完全なコピーを新しい子プロセスとして生成するが、物理的に全てのデータをコピーするのはかなりのオーバーヘッドになるため、コピー・オン・ライト(CoW)と呼ばれる仕組みを採用している
  • CoWは書き込みが必要となるまでメモリを実際にコピーしない。それまでの間、親プロセスと子プロセスとはメモリ上の同じデータを物理的に共有しており、親または子のいずれかで変更する必要が生じた時のみメモリをコピーすることで両者のプロセスの独立性を保っている
  • CoWはfork(2)で子プロセスを生成するときにリソースを節約できるのでとても便利な仕組みである
 

第14章 プロセスは待てる

  • 孤児プロセスが発生しないようにProcess.waitを使えば、子プロセスのいずれか1つが終了するまで親プロセスをブロックして待つことができる
  • 子プロセスを活用するのは、Unixプログラミングでよく使われるパターンである。こうしたパターンは子守りプロセスやマスター/ワーカー、preforkなどと呼ばれている
  • このパターンの肝は用意した1つのプロセスから並列処理のために複数の子プロセスを生成して、その後は子プロセスの面倒を見るというもの。子プロセスたちが応答するのかを確かめたり、子プロセスが終了した際には後始末をする
  • WebサーバのUnicornがこのパターンを採用している。Unicornではサーバ起動時にワーカープロセスをいくつ使うかを指定する。5つのインスタンスが必要だと指定した場合、unicornプロセスは起動後にWebリクエストをさばくための子プロセスを5つ生成する。親プロセスは子プロセスそれぞれの死活監視を行い、子プロセスがちゃんと応答できるようにする
 

第16章 プロセスはシグナルを受信できる

  • シグナルは非同期通信。プロセスはカーネル(OSの核となるプログラムで複数のプロセスを同時に実行するためのスケジュール管理やメモリの管理を行うもの)からシグナルを受けたとき、①シグナルを無視する、②特定の処理を行う、③デフォルトの処理を行う、のいずれかの処理を行う
  • シグナルはある特定のプロセスから別のプロセスへと送られるものであり、カーネルはその仲介役となっている
  • pidさえ分かれば、システム上のどのプロセスともシグナルで通信できる。シグナルはプロセス間の通信手段である
  • 現実世界でのシグナルは、サーバやデーモンといった長期間動き続けるプロセスで使われているのがほとんどである
 

第17章 プロセスは通信できる

  • プロセス間通信(IPC)で複数のプロセス間で情報をやりとりできる
  • パイプは単方向のデータの流れ。パイプを開くというのは、プロセスの片方の端を別のプロセスの片方の端につなぐことをいう。パイプを通じてデータを流すことができるようになるが、その方向は単方向に限られる。プロセスがパイプに対して書き込みではなく読み込み側となることを宣言した場合、そのパイプには書き込めない。逆も然り
  • パイプが単方向の通信手段であるのに対し、ソケットは双方向の通信手段である。親のソケットは子のソケットに読み書きできるし、逆も然り
 

第18章 デーモンプロセス

  • デーモンプロセスは、ユーザーに端末から制御されるのではなく、バックグラウンドで動作するプロセスである。デーモンプロセスのよくある例としては、Webサーバやデータベースサーバのように、リクエストをさばくためにバックグラウンドで常に動作するプロセスがある
  • デーモンプロセスはオペレーティングシステムの核でもある。さまざまなプロセスがバックグラウンドでずっと動作し続けているおかげで、システムは正常に動いている
  • プロセスを永遠に応答し続けられる状態にする必要がある場合にデーモンプロセスを用い、不要ならcronジョブなどのバックグラウンドジョブの仕組みを検討する
 

第19章 端末プロセスを作る

  • execve(2)は現在のプロセスを異なるるプロセスに置き換えられる。RubyプロセスをPhyhonや他のRubyプロセスに変えることができる
  • execve(2)によるプロセスの置き換えは元に戻すことができない
  • fork + execveの組み合わせは新しくプロセスを生成する際によく使われる。fork(2)で新しく子プロセスを生成して、それからその子プロセスを任意のプロセスに置き換えるためにexecve(2)を使う
 

第20章 おわりに

  • カーネルから見れば、どのプログラミング言語で書かれたプログラムも同じに見える。最終的には、すべてのコードはカーネルが理解可能な単純なものへコンパイルされる。そして、動作する段階ではすべてがプロセスとして同等に扱われる
  • Unixプログラミングはプログラミング言語を問わない。RubyスクリプトとCプログラムを連携させることもできる
  • シグナルを使えば、システム上のどんなプロセスも互いに情報を伝達できる。プロセスに名前を付けることで、コマンドライン上から誰でもプログラムの状況を検査できる。終了コードを使えば、呼び出し元のプロセスに成功/失敗のメッセージを伝えられる
 

付録A Resqueのプロセス管理

  • ResqueはRuby製の有名なジョブキューである
  • Resqueワーカーの仕事は、起動してアプリケーション環境を読み込んだらRedisに接続し、保留中のバックグラウンド・ジョブの実行を予約すること
  • Resqueワーカーはメモリ管理のためにfork(2)を使っている。Resqueはfork(2)を使うことで、ワーカープロセスのメモリ使用量が肥大化しないようにしている。Resqueがジョブを実行した後、メモリの使用状況は毎回ジョブの処理を行う前のきれいな状態に戻っている。メモリ解放の仕組みであるGCガベージコレクション)には課題があるため、fork(2)を使う方がメモリの使用量を抑えることができる
 

付録B Unicornのワーカープロセスの管理

  • Unicornは可能な限り仕事をカーネルに任せようとするWebサーバである
  • Unicornはパフォーマンスも信頼性も高く、GithubなどのRubyで作られた巨大なWebサイトでも数多くの採用事例がある
  • Unicornは大きく分ければpreforkサーバというカテゴリに属する。Unicornを起動する際にはワーカープロセスがいくつ必要なのかをUnicornに伝える必要がある。Unicornは起動するとまず、ネットワークソケットを初期化してアプリケーションをロードし、fork(2)を使ってワーカープロセスを生成する。
  • Unicornのマスタープロセスは、ワーカープロセスの死活監視をしたり、ワーカーがリクエストの処理に時間がかかりすぎていないかを検査する
 

付録C preforkサーバ

  • preforkは複数のプロセスを個別に生成するよりもメモリを効率的に使うことができる
  • 例えば、Unicornのワーカーを10個立ち上げるには11個のプロセスが必要になる。そのうち1つのプロセスがマスターとなり、他の10個のワーカープロセスの子守りをする。Unicornは起動時にマスタープロセスだけがRailsをロードするため、カーネルリソースの取り合いは発生しない
  • マスタープロセスはRailsをロードするのに3秒ほどかかるが、プロセスを10回フォークするのには、さほど時間はかからない。マスタープロセスはRailsを読み込むことで70MBのメモリを消費するが、コピー・オン・ライトのおかげで、子プロセスはマスタープロセスが使用しているのと同じデータについてはメモリを一切消費しなくて済む
 

おわりに

いかがでしたでしょうか。馴染みのない言葉ばかり出てくるので難しいですよね。

粘り強く読書を継続して、知識をつけていきましょう、、、!

 

引き続き、こちら(↓)の記事で紹介した本を順番にアウトプットしていく予定です。

ysk-pro.hatenablog.com

是非参考にしてみてくださいー!

来週もよろしくお願いします!