おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

daemonの作り方

先日webrickのソースを少し読んだのですが、daemonを作る部分がよくわかりませんでした。

def Daemon.start
  exit!(0) if fork
  Process::setsid
  exit!(0) if fork
  Dir::chdir("/")
  File::umask(0)
  STDIN.reopen("/dev/null")
  STDOUT.reopen("/dev/null", "w")
  STDERR.reopen("/dev/null", "w")
  yield if block_given?
end

具体的にわからない部分を書くと

  • Process::setsidってなに?
  • なんでfork二回もやってるの?
  • その後のchdirとかumaskとかreopenとかは何でやってるんだろう?

のようなところ。つまりほぼ全部ですね・・・><


そこでいつものように調べました。

Process::setsidってなに?

新しいセッションを生成するメソッド

セッションとは
  • ユーザがログインした時に作られるプロセスの集合。
  • 各セッションは基本的にひとつの制御端末を持つ。
  • 制御端末を持たないセッションはどのユーザの制御下にもなく、daemonプロセスと呼ばれる。

なんでfork二回もやってるの?

一度目のfork

Process::setsidを成功させるため。

まず、セッションを生成するプロセスはプロセスグループ*1のリーダー"以外"でなくてはならないという決まりがあります*2。そして、新規プロセスは新しいプロセスグループにプロセスリーダーとして配置されるそう。通常、プログラムは起動時に新しくプロセスが作られるので、最初にforkをしておかないと

デーモンを作るプログラムを起動→そのプロセスがプロセスリーダーになる→プロセスリーダからProcess::setsidが呼ばれる→セッション作成失敗

となるわけです。なのでforkすることによってリーダー"ではない"プロセスを作り、そのプロセス上でProcess::setsidを実行しています。

二度目のfork

制御端末を関連づけできなくさせるため。

セッション内には、セッションリーダーと呼ばれるプロセスがあり、このプロセスが制御端末とやりとりをします。

Process::setsidにより作られたセッション(およびセッションリーダー)は制御端末を持っていないのですが、セッションリーダーが端末デバイスを開くと、それが制御端末になってしまうのだそう。そこでセッションリーダーのプロセスを消すことによって、制御端末を関連づけさせることができなくなります。

その後のchdirとかumaskとかは何でやってるんだろう?

chdir

どのディレクトリも使用中ではないことを確実にするため。この処理をしないと、デーモンのカレントディレクトリは操作ができなくなってしまうらしい。

umask

プログラム中でファイルを生成する場合のumask値を揃えるため。ファイルを作らない場合などは特に設定しなくても問題ないようです。

reopen

IO#reopen(path)は、指定されたファイルにストリームを繋ぎ換えるメソッド
標準入出力および標準エラー出力は親プロセスから引き継がれるおり、どこへリダイレクトされているかわからないため、安全のためにいったん/dev/nullへリダイレクトしているようです。

ちなみにRuby1.9だと

Process.daemonが使えるようになっているので、上のコードは書かなくてもいいみたい。
Process - Rubyリファレンスマニュアル

*1:プロセスグループとはプロセスを1つ以上まとめたもので、シグナルを複数のプロセスに配布するために使用される

*2:これは、プロセスグループIDとプロセスリーダーのIDが等しいのと、セッションを作成したプロセスがプロセスリーダーになるという仕様が原因。プロセスリーダーが新しくセッションを作ると、プロセスグループIDが重複してしまう。