『なるほどUnixプロセス』を読んだ

読みました。

tatsu-zine.com

どんな本か

主にRubyの Process モジュールを使いながらUnixプロセスについて知る本です。

Rubyでには Process モジュールを通じてUnixプロセスを操作することができます。つまり、プロセスに関するUnixのシステムコールをRubyから扱うことができます。この点で、Cから直接システムコールを発行するよりRubyプログラマにとっては読みやすいコードで、Unixプロセスについての解説を読むことができます。実際、Ruby製のWebアプリケーションでよく使われるResqueやUnicornも、Rubyでプロセスを操作しています。

原著の出版は2011年と少し古いですが、主題がそうそう古びるものではないので、出版年についてはほとんど問題にならないと思います。

具体的には次のようなことがらが説明されています。

  • プロセス自体の特徴
  • forkと子プロセスとの協調
  • シグナル
  • IPC(パイプ、Unixドメインソケット)
  • 外部コマンドを実行するプロセスの生成
  • 付録として実例の説明
    • Resque, Unicorn, Spyglass(この本オリジナルのpreforkサーバ)

なぜ読んだのか

最近はわりとアプリケーションレベルの知識に偏ってインプットしていたので、日常的にあまり意識しないOSレベルの知識についてインプットしたかったというのがあります。Rubyをふだん書いている身として、RubyでUnixのことについて説明しているところに興味を持ちました。また、ちょうど達人出版会でセール中だったからというのも理由のひとつです。

実際に読んでみると、1章1章がちょうどいい詳細度の説明になっていることと、自然な日本語に訳されていることから、たいへん読みやすかったです。

どうだったか

読んでみて興味深かった点をいくつか挙げます。

子プロセスの待機

Rubyでは fork(2) を呼び出す Kernel.#fork を使うと、子プロセスを作ることができます。そして、作った子プロセスが処理を終え、終了コードなどを返すのを待つには、waitpid(2) を呼び出すRubyの Process モジュールのAPIを使うことができます。しかも、子プロセスを待つシステムコールとしての waitpid(2) に対して、Ruby側のAPIとしては次のバリエーションがあるのがおもしろいところです。

このあたりは、ライブラリ利用時にできるだけ意図が明確になるようにする、という設計思想を感じます。

ゾンビプロセス

fork(2) を使って子プロセスを作ったあと、Unixカーネルは終了した子プロセスの情報をキューに入れて管理します。Rubyでは、この子プロセスの情報は Process.#wait またはそれに類するメソッドによって取り出すことができます。この情報が取り出されずに親プロセスが終了したり、親プロセスの処理がなかなか終わらないとき、キューに子プロセスの情報が残ったままになります。この場合、この子プロセスはゾンビプロセスと呼ばれます。これまでゾンビプロセスは「pstop を叩いたときに表示されるなにか」という意識しかなかったのですが、ここの説明で「なるほど」という気持ちになりました。

ゾンビプロセスはカーネルリソースの無駄使いとなるので、子プロセスを待たなくてよい場合、Rubyでは Process.#detach でスレッドを生成して、そちらのスレッドに待たせるのがよしとされているようです。

デーモンプロセス

デーモンといえば、バックグラウンドで立ち上がっているサーバの状態のことをそう呼んでいるという認識でした。たとえば、Rackサーバを次のように起動すると、デーモンプロセスとなり、起動後に制御が端末に戻ってきます。

$ rackup -D config.ru

プロセスをデーモンプロセスと呼ばれる端末から切り離されバックグラウンドで動くものにするには、次の手順を踏む必要があります。

  • forkしたあと、親プロセスで exit することで、子プロセスをわざと孤児にする
    • 親プロセスを終了させて、制御を端末に戻す
  • setsid(2) でその子プロセスをプロセスグループとセッショングループのリーダーにする
    • これをやらないと、端末などからSIGINTなどのシグナルが送られるとき、その子プロセスが終了してしまう
  • ふたたびforkして、親プロセスで exit することで、子プロセスをわざと孤児にする
    • この時点で、新たに作った子プロセスはセッションリーダーではなく、制御端末からも完全に切り離されている
  • カレントディレクトリが削除される場合の対策として、ルートディレクトリに移る
  • 標準ストリームをすべて /dev/null にする
    • 端末につながってないので持っていてもしかたない

また、Rubyでは Process.#daemon というメソッド一発でプロセスをデーモンにすることができます。そして、 Process.#daemon の中 ではCで上の手順をまさに実行しています。

デーモンプロセス化はもっとカーネルの機能一発で実現されているかのようなイメージを抱いていたので、このような細かい手順を踏んで実行することで成り立っているというのが、個人的には意外でおもしろかったです。

おわりに

プロセスというUnixにおいて動作するプログラムを抽象化した存在について知っておくことは、Rubyを書くこと以外の場面でも活きてくると思いますし、単純に世界が広がっておもしろいので、Rubyが読める人はこの本も読んでおくとよさそうだと思いました。サンプルコードで使われるライブラリはすべてRubyの組み込みライブラリか標準添付ライブラリの範疇に収まっているので、気軽に実行できます。実際に手を動かして読むと「なるほど」感が深まると思います。

また、preforkサーバの見本として付録についているSpyglassのコードを読んでみたところ、この本で出てきた知識をフル活用していて、かつ見本ということでシンプルなコードになっているので、やってることがわかるという感覚が得られたのがよかったです。コードはほとんどRubyで書かれています(HTTPパーサなど一部がC)。このサーバのコードをひととおり把握することで、Unicornなど本番で使われるWebサーバの内部の話題にもついていきやすくなりそうだと思いました。

原著者Storimer氏の続編として、TCPソケット編とRubyスレッド編があるようなので、日本語版はないものの、こちらにも手を出したいところです。

Books by Jesse Storimer | Short, focused technical books about system programming specifically for Ruby and Rails developers.