Rubyで学ぶUNIXデーモンの仕組み――理論と実装のステップバイステップ解説
UNIXデーモンとは、バックグラウンドで動き続けるプログラムのことです。Nginx、PostgreSQL、OpenSSHなどがその代表例として挙げられます。デーモンはいくつかの特殊なテクニックを使ってプロセスを「切り離し(detach)」し、どの端末からも独立した状態で動作できるようにしています。
筆者は昔からデーモンに何かと魅了されてきました(名前の響きのせいかもしれません)。そこで今回は、デーモンがどのように動作するのか、そしてRubyでどうやって作れるのかを解説します。
……ただし、その前に。
本番環境では真似しないでください!
正直なところ、自分でデーモンを作りたい場面はほとんどないはずです。同じ目的を達成するなら、もっと簡単な方法があります。
「バックグラウンドで動くプログラムが欲しい」だけであれば問題ありません。OSには、通常のプログラムをバックグラウンドで実行するための仕組みが標準で用意されています。
UbuntuであればUpstartやsystemd、Mac OS Xであればlaunchdがそれに該当します。その他にも類似の仕組みがありますが、考え方はどれも共通しています。長時間動かし続けるプログラムの起動・停止方法を設定ファイルに記述すれば、あとはservice my_app startのようなシステムコマンド一つでバックグラウンド実行が可能になります。
要するに、Upstartのような仕組みはシンプルで信頼性が高い一方、古来からの手作りデーモンは難解で、正しく実装するのが非常に難しいのです。
「それなら、なぜわざわざデーモンについて学ぶ必要があるのか?」——答えはシンプル、楽しいからです! しかもその過程で、Unixプロセスに関する興味深い知識が数多く身につきます。
最もシンプルなデーモン
「デーモンを作るな」と言ったそばから、早速デーモンを作ってみましょう。Ruby 1.9以降なら驚くほど簡単で、Process.daemonメソッドを呼び出すだけです。
# おまけ: プロセス名を打ちやすい名前に変更しておく
$PROGRAM_NAME = "rubydaemon"
# 現在のプロセスをデーモン化する
Process.daemon()
# 1秒ごとに現在時刻をファイルに記録する
loop do
File.open("/tmp/rubydaemon.log", "a") { |f| f.puts(Time.now) }
sleep(1)
end
このスクリプトを実行すると、即座に制御がコンソールへ戻ってきます。ログをtailしてみると、期待どおり1秒ごとにタイムスタンプが追記されているのが確認できます。

とても簡単ですね。しかし、これだけではデーモンの仕組みは説明できません。本当に理解するには、デーモン化の処理を手作業で一から実装してみる必要があります。
親プロセスを変更する
bashから普通のプログラムを実行すると、そのプロセスはbashの子プロセスになります。ところがデーモンの場合、起動方法にかかわらず、親プロセスは常にOSが提供する「ルート」プロセスになります。
これはデーモンの親プロセスID(PPID)を見れば分かります。デーモンの親プロセスIDは常に1です。以下の例では、pstreeコマンドを使ってその様子を確認しています。
$ pstree
-+= 00001 root /sbin/launchd
|--- 72314 snhorne rubydaemon
興味深いことに、これは「孤児プロセス(orphaned process)」の姿でもあります。孤児プロセスとは、親プロセスがすでに終了してしまった子プロセスのことです。
つまり、デーモンを作るということは、意図的にプロセスを孤児化させるということです。以下のコードはまさにそれを行っています。
# おまけ: プロセス名を打ちやすい名前に変更しておく
$PROGRAM_NAME = "rubydaemon"
# 子プロセスを生成し、親プロセスを終了する。
# これによりプロセスが「孤児化」し、デーモンとなる。
exit if fork()
# 1秒ごとに現在時刻をファイルに記録する
loop do
File.open("/tmp/rubydaemon.log", "a") { |f| f.puts(Time.now) }
sleep(1)
end
forkを呼び出すと、同じコードを実行する2つのプロセスが誕生します。元のプロセスが新しく生まれたプロセスの親です。forkは親プロセスに対しては真の値を、子プロセスに対しては偽の値を返します。したがってexit if fork()は親プロセスだけを終了させることになります。
現在のセッションから切り離す
しかしこの「デーモン化」コードにはまだ問題が残っています。プロセスの孤児化には成功しても、まだ端末のセッションに属したままなのです。つまり、端末を終了するとデーモンも一緒に死んでしまいます。これを解決するには、新しいセッションを作成して再度forkする必要があります。Unixのセッションやプロセスグループに馴染みがない方は、StackOverflowの解説記事などを参照するとよいでしょう。
# おまけ: プロセス名を打ちやすい名前に変更しておく
$PROGRAM_NAME = "rubydaemon"
# 子プロセスを生成し、親プロセスを終了する。
# これによりプロセスが「孤児化」し、デーモンとなる。
exit if fork
# 新しいセッションを作成し、その中でさらに子プロセスを生成して
# 現在のプロセスを終了する。
Process.setsid
exit if fork
# 1秒ごとに現在時刻をファイルに記録する
loop do
File.open("/tmp/rubydaemon.log", "a") { |f| f.puts(Time.now) }
sleep(1)
end
STDIN・STDOUT・STDERRの付け替え
上記のコードにはもう一つ問題があります。既存のSTDOUTなどがそのまま残っている点です。端末からデーモンを起動すると、デーモンがSTDOUTに書き出した内容がすべてあなたの端末に送られてきてしまいます。これは望ましくありません。
実は、STDIN・STDOUT・STDERRは任意のパスに付け替えることができます。ここでは/dev/nullへリダイレクトします。
# おまけ: プロセス名を打ちやすい名前に変更しておく
$PROGRAM_NAME = "rubydaemon"
# 子プロセスを生成し、親プロセスを終了する。
# これによりプロセスが「孤児化」し、デーモンとなる。
exit if fork
# 新しいセッションを作成し、さらに子プロセスを生成して終了する。
Process.setsid
exit if fork
STDIN.reopen "/dev/null"
STDOUT.reopen "/dev/null", "a"
STDERR.reopen '/dev/null', 'a'
# 1秒ごとに現在時刻をファイルに記録する
loop do
File.open("/tmp/rubydaemon.log", "a") { |f| f.puts(Time.now) }
sleep(1)
end
ワーキングディレクトリを変更する
最後の問題は、デーモンのワーキングディレクトリが「起動時にいたディレクトリ」のままになっていることです。後になってそのディレクトリを削除してしまうかもしれないので、これはあまり良い状態とは言えません。そこで、ディレクトリを/(ルート)へ変更しておきましょう。
# おまけ: プロセス名を打ちやすい名前に変更しておく
$PROGRAM_NAME = "rubydaemon"
# 子プロセスを生成し、親プロセスを終了する。
# これによりプロセスが「孤児化」し、デーモンとなる。
exit if fork
# 新しいセッションを作成し、さらに子プロセスを生成して終了する。
Process.setsid
exit if fork
STDIN.reopen "/dev/null"
STDOUT.reopen "/dev/null", "a"
STDERR.reopen '/dev/null', 'a'
Dir.chdir("/")
# 1秒ごとに現在時刻をファイルに記録する
loop do
File.open("/tmp/rubydaemon.log", "a") { |f| f.puts(Time.now) }
sleep(1)
end
どこかで見たことがあると思いませんか?
実は、この一連の手順こそが、Process.daemonメソッドがRuby本体に追加される以前に、あらゆるRubyデーモンが実行していた処理なのです。筆者はこのコードを、Rails 4.xで削除されたActiveSupportによるProcessモジュール拡張から、ほぼ一行ずつそのままコピーしました。当時のメソッドの実装は、GitHub上のコードで確認できます。
-
Rubyで学ぶニューラルネットワーク超入門――思っているほど難しくない!
この記事では、ニューラルネットワークの基礎と、Rubyを使った実装方法を学びます。人工知能やディープラーニングに興味はあるものの、何から始めればいいのか分からない方にぴったりの内容です。シンプルな例題を通じて、重要な概念をわかりやすく解説していきます。実際にRubyで多層ニューラルネットワークを書くことはほとんどありませんが、コードがシンプルで読みやすいため、内部で何が起きているのかを理解するには最適なアプローチです。まずは少し立ち止まって、私たちがここに至るまでの道のりを見てみましょう。 映画『エクス・マキナ』のワンシーン 『エクス・マキナ(Ex Machina)』は2014年に公開され
-
Ruby 2.6の新機能9選|コード例でわかる注目ポイントを徹底解説
Ruby 2.6には、開発者の生産性を高める新しい機能やパフォーマンス改善が多数盛り込まれています。 本記事では、Ruby 2.6で導入された9つの注目新機能を、実際のコード例とともにわかりやすく紹介します。最新のRuby動向をキャッチアップしたい方は、ぜひ最後までご覧ください。 1. 無限Range(Endless Range) Ruby 2.5以前でもFloat::INFINITYを使えば終端のない範囲を表現できましたが、Ruby 2.6ではさらに直感的な記法が使えるようになりました。 新しい無限Rangeは次のように書きます。 (1..) 通常のRangeが(1..10)のように終端