前回のつづき。なるほどUnixプロセス ― Rubyで学ぶUnixの基礎 - 達人出版会という本の読書メモ。前回のが意外なほどに読まれていてビビっている。
今回はプロセスと情報をやりとりする方法についてまとめる。プロセスレベルでの情報のやりとり、について。
「プロセスとプロセスレベルでの情報をやりとりする方法」には、以下のような方法がある。
- 環境変数
- 引数
- 名前
- 終了コード
これをひとつずつ見ていく。
環境変数
日頃よく使っている「環境変数」。キーとバリューが対になっている、プロセスで扱うことのできるデータを保持しているもの。
「プロセスで扱うことができる」とはどういうことか。
すべてのプロセスは自身が生成される際、親プロセスから環境変数を引き継ぐ。環境変数は親プロセスによって設定され、子プロセスに引き継がれる。
環境変数はプロセスごとに存在し、それぞれのプロセス内ではグローバルにアクセスできる。あるプロセス内で設定された環境変数に別のプロセスからアクセスすることはできない(環境変数はプロセスごとに存在)が、同一プロセスであればいつでもどこでもアクセスすることができる。
$ export MESSAGE='This is a value' $ ruby -e "puts ENV['MESSAGE']"
たとえばこれ↑は、
している。もちろんアクセスできる(環境変数は子プロセスに引き継がれる)。
ENV['MESSAGE'] = 'This is a value' system "echo $MESSAGE"
↑この場合は逆で、
している。これもアクセス可能。
このような環境変数の使い方の実用例としてよく目にするものは $ RAILS_ENV=production rails server
かな。このように環境変数は、コマンドラインツールに入力を渡す方法としてよく用いられる。
環境変数を扱うようなシステムコールは存在しないが、Cサブルーチン(ライブラリ関数)である setenv(3)
や getenv(3)
で扱うことができる。environ(7)
で環境変数の概要を知ることもできる。ちなみにセクション7は「その他」とのこと(manページ - Wikipedia)。「セクション」がなんのことだかわからない場合は前回のエントリを参照。
引数
ここでは Ruby のケースから見ていく。
Ruby には ARGV という、コマンドラインから Ruby プロセスに渡された引数を格納するための特別な配列が用意されている。
以下のような Ruby のコードを、
p ARGV
argv.rb というファイルとして保存したとして、以下のように実行すると、
$ ruby argv.rb foo bar -va
以下のような出力が得られる。
["foo", "bar", "-va"]
このように ARGV は、プログラムにファイル名を渡したい場合やコマンドライン引数の解析のためによく用いられる。
ここでは Ruby での例だったが、どの言語にも何かしらの argv
に相当するものが用意されている。argv
とは、「引数の配列」を意味する argument vector
の略。argv
には、コマンドラインからプロセスに渡された引数が格納されている。
僕らが自分でロジックを考え、それをプロセスとして実行させる際には、きっとほぼ全てのケースにおいて何かしらの言語を用いることになる。操ろうとしているプロセスに引数で情報を与えたいときや与えたはずの情報を取り出したいときには、この argv
の存在を思い出そう。
名前
「情報をやりとりするための方法」としてまさか「名前」が出てくるとは。というかんじ。でもまぁたしかに、名前も立派な、そして大事な「情報」であることに違いない。
というのもUnixプロセスには、「プロセスの状態」を知らせるための手段が殆どない、んだそうだ。
例えばプロセスから発信される情報を「ログファイル」という形で書き出すことで残したり、ソケットを開いてネットワークを使うことで他プロセスと通信したりすることはできる。でも、これらはいずれも「プロセスレベルでの情報のやりとり」ではない。
そんななかでプロセス名は、立派な「プロセスレベルでの情報伝達手段」のうちのひとつ。すべてのプロセスには名前がある。プロセス名は実行時に変更できるので、これを使ってプロセス自身の情報を伝えることができる。
Ruby では $PROGRAM_NAME
に現在のプロセス名が格納されていて、このグローバル変数を変更すればプロセス名を変えることもできる。
確認してみる。irb を起動して、
puts $PROGRAM_NAME # => irb $PROGRAM_NAME = 'hogefuga' # => "hogefuga" puts Process.pid # => 68723
としてから、 ps
でプロセス名を確認してみる。
$ ps -p 68723 PID TTY TIME CMD 68723 ttys009 0:00.25 hogefuga
たしかにプロセス名の変更ができている。
プロセス名による情報のやりとりを実践的に行っている実装には、たとえば Resque(Ruby製のジョブキュー)がある。親プロセスから子プロセスを生成するときに、親子両方のプロセス名を変えるということが Resque の中では行われている。
このことは、この本の付録で紹介されているんだけど、残念ながら「何のために親子のプロセス名を変更しているのか。その理由」については明確に書かれていなかったし、この本以外のものを少し調べてもみたけどよくわからなかった。
「情報のやりとり」にフォーカスが当たっているので、他プロセスに伝達する必要のある情報をプロセス名に格納してそれを他プロセスで読み込んで...
、、って感じのものを想像していたのだけど、どうやらそうではなさそう。単に、人間がプロセス一覧を見たときに「どれが Resque の親プロセスでどれが子プロセスか」を判別しやすくするためにプロセス名を変更している、っていうかんじなのかな。もちろんそれも立派な「情報伝達」に違いはないのだけど。
終了コード
プロセスレベルでの情報伝達手段のうちのもうひとつが終了コード。あらゆるプロセスは、正常終了か異常終了かを示す終了コード値(0 〜 255)と共に終了する。
プロセスの正常終了時には終了コード 0
を返すことが慣習としてある。0
以外の終了コードはエラーを示す。この慣習から敢えて外れることもできるが、従っておくことで他の Unix ツールとの連携もしやすくなる。
Ruby の場合、Ruby プロセスを終了させる方法にはいくつかある。
Kernel#exit
- 引数なしでの実行で、終了コード
0
でプロセス終了する - 引数を与えればその数値を終了コードとしてプロセス終了する
- 引数なしでの実行で、終了コード
Kernel#exit!
- Kernel#exit と殆ど同じだが、引数なしの実行で終了コード
1
でプロセス終了する - 引数を与えればその数値を終了コードとしてプロセス終了することもできる
- Kernel#exit と殆ど同じだが、引数なしの実行で終了コード
Kernel#abort
- 問題のあったプロセスを終了させる場合に使用する。終了コード
1
でプロセス終了する - 引数でメッセージを渡すことができる。
- 渡したメッセージはプロセスの終了前に標準エラー出力に出力される
- 問題のあったプロセスを終了させる場合に使用する。終了コード
Kernel#raise
- 他の方法とは異なり、すぐにはプロセス終了しない。例外を送出するためのもの
- 例外は単に呼び出し元へ向かって送出されるだけ、最後まで捕捉されなかった場合、結果としてプロセスが終了する
- 最後まで捕捉されなかったときの挙動は abort と似ている。
その実装意図を具体的に表現できるように、様々な方法が取り揃えられている。
終了コードを用いることで、親プロセスから派生した子プロセスが処理を行った結果どうだったのかがわかるし、それを判別して後続の処理を分岐させることもできる。やはりこれも立派な情報伝達の手段のひとつだ。
以上
以上が「プロセスとプロセスレベルでの情報をやりとりする」ための4つの手段についてのお話。
続いて、プロセスの適切な扱い方について見ていく。