すがブロ

sugamasaoのhatenablogだよ

デーモンについての覚書

ちょっと会社でデーモンとは、みたいのを話す機会があったので整理がてらメモ。本当はコードも合わせて載せたかったけど、時間がなくて断念したw
あやふやな部分もあるので識者の方にはぜひ訂正をお願いしたく……!

デーモンとは?

ぶっちゃけて言えば無限ループのプログラムなんだけど、詳しくは http://ja.wikipedia.org/wiki/デーモン_(ソフトウェア) とかを見ると良いだろう。

自分で作るのであればおおまかに以下の点を気をつければ良いかなぁと思う。

  • プロセスを端末から切り離す
  • 入出力を一度閉じる(使うなら明示的にオープンしたものを使おう)
  • SYSLOGなり何がしかの出力ファイルに動作を出力する
  • SIGNALを受け取る
  • PIDファイルを作る

厳密にやろうと思うとumaskを0にするだとか、'/'に移動するだとかもあったりするけど、ゆとりなのでプロセスから切り離すを含めdaemon関数(man daemonせよ)を使えばよろしい。なので、後半の三点に注力すれば良い。気がする。

PIDファイルやログファイルといったファイル出力はデーモンになってから普通にファイルオープンなりsyslog系の関数でファイルに書きだせば良い。PIDファイルというのはプロセスIDを書きだすだけなんだけど、forkした後*1に生成しないと、起動時のプロセスID*2を出力してしまうので注意な。

SIGNALはイベントハンドラ見たいなもんだ。今日びのウェッブプログラマーはそれで十分伝わるよね。

プロセスを切り離す事の何が重要か?

端末から起動したプロセスは基本的にその端末のプロセスグループに属する。そうすると端末を急に落としたりして親が先に死んだ時にそのプロセスの終了ステータスが回収されずにゾンビプロセスになるっぽい。が、ここは実はよくわかっていない。親が死んだら自動的にinitグループ傘下になるきがするし、実は問題ないのでは?的な。
自分で子プロセスを産んで管理する場合を考慮したお作法なのかな?(コメント欄も一読ください)

initグループは傘下のプロセスのwaitをきちんと見てくれるので、initグループ傘下にいればゾンビプロセスになってもすぐに終了ステータスを回収してくれるので、どんな状況になるかわからんところで動くよりは明示的であろう、とは思う。

いまゾンビプロセスの話をさらっと出したけれど、そこについてちょっと説明しようか。

Unixのプロセスは人間の世界とは異なり、親は子供が死ぬまで面倒を見る。子供は終了したら終了ステータス*3を親に回収されるのを待つ。そして、親プロセスは子プロセスに対して、プロセスIDから子供を辿り「お前は死んだのか?死んだのならステータスをよこせ」と明示する必要がある。ここをサボって、子供は終了しているけれど親が回収しない状態になると、子供はプログラムの終了とプロセスの終了の中間生物となってそれがつまりゾンビプロセスと呼ばれる状態になる。

いまどきはそこまで神経質になる事はないかもしれないけれど、プロセスを作成するということはプロセステーブルを消費することなので http://ja.wikipedia.org/wiki/Fork爆弾 っていう攻撃もあることだし、きちんと子供を処理したほうが良いであろう(その昔、図らずしもFork爆弾を無限ループでやってしまって開発サーバを止まらせたことがあるw)。

補足しておくと、bashzshだと、コマンド起動時に & を付けてやるーーいわゆるバックグラウンドプロセスにする、ってのをやると端末から切り離してくれるらしい。ただ、それはイケテルシェルのイケテル機能なので、簡易的なものと考えておいたほうが良いだろう。

SIGNALを受け取る

SIGNALが投げられたら*4、受信したプロセス内でイベントハンドラに紐づけられている関数が即座に呼ばれる。即座に呼ばれるもんで、例えばファイルに書きだすループ中とかでも平気で呼ばれる。そこでクリックイベ ントよろしくSIGTERMが投げられたからexitしよーとかやってしまうとそれはもうひどいことになる*5
なので、グローバル変数なりに終了するぞフラグを持たせておいて、ファイルを書き終えたタイミングなど、プロセスのメインループ内の都合の良い場所でフラグを見て終了するなり、設定ファイルを再読み込みするなりのSIGNALにそった動作をするようにしよう。

ちゃんと運用するなら

PIDファイルとログファイルはちゃんと作ろう。PIDファイルは監視する対象のプロセスIDを特定するために必要だ。ログファイルが無かったらプロセスの挙動が簡単にはわからなくなるしな*6
また、SIGNALで意外と重要なのがUSR1だ。newsyslogdとかlogrotatedを使ってログファイルのローテーションをする時、一般的にはPIDファイルに書かれたプロセスIDに対して、ファイルローテーション時のSIGNALとしてUSR1を投げる。ので、ファイルのリオープンなどを考える必要があるのだ。

*1:またはdaemon関数を呼んだ後

*2:メインで動くプロセスの親プロセスのものだ

*3:いわゆるexitコードだ

*4:おまいらの大好きなkill -9とかのアレだぞ

*5:ファイルのクローズとか

*6:がんばってプロセスにアタッチすればできるかもしれないけど、常人にはお手軽な方法とは言えない