Scyphus Draft — デーモンの作り方

プログラマであれば一度は自分で作ってみたいデーモンプロセス. 本稿では,デーモンの作り方と関連するTipsを連載にて解説していきます. なお,本稿ではC言語を用いて説明します.


プロセスのデーモン化

更新日: 2011年2月28日
著者: 浅井 大史

デーモンプロセスの特徴

デーモンは通常のバックグラウンドプロセスとは異なり次の特徴を持っている.

デーモン化ユーティリティ・関数

簡単にプロセスをデーモン化する方法として,FreeBSDには daemon(8) というコマンドが用意されている. また,BSD系のOSには daemon(3) 関数が用意されており,簡単にプロセスのデーモン化を実現できる. しかし,これらのコマンドや関数の実装は非常にシンプルであり,またプロセス毎の用途・要望に対する拡張性や移植性を考えると各自で実装した方が便利であるため,本稿ではこれらの既存のコマンドや関数については取り扱わない.

プロセスのデーモン化の手順概要

ここではプロセスのデーモン化(上述の特徴を持ったプロセスにすること)の手順を概説する. なお,上述の特徴以外に多くのプロセスのデーモン化においては,通常, chdir(2) によるカレントワーキングディレクトリの変更, および,dup2(2) による標準ストリーム (stdin/stdout/stderr) 出力先の設定が行われるため,これも含めて説明する.

  1. プロセスを制御端末 (tty) から切り離す
  2. fork(2) および exit(3) によりバックグラウンドプロセスにする
  3. chdir(2) によりカレントワーキングディレクトリをシステムルート ("/") にする. この操作は,プロセスのデーモン化後,カレントワーキングディレクトリの属するファイルシステムをアンマウントできなくなることを防ぐためである.
  4. umask(2) によりファイル作成モードマスクに 0 をセットする.
  5. ファイルディスクリプタを全てクローズする. リダイレクトの設定を行っている(可能性がある)場合,標準ストリーム (stdin/stdout/stderr) もクローズし再度オープンする.
  6. stdin/stdout/stderr/dev/nullへのリダイレクトに設定する. (ログファイルなどへの出力は次回以降に説明予定である.)

ソースコード解説

ソースコードは以下のリンクにも設置してあります.

ヘッダファイルの読み込み

/*_
 * Copyright 2009, 2011 Scyphus Solutions Co. Ltd.  All rights reserved.
 *
 * Authors:
 *      Hirochika Asai
 */


/* $Id$ */

#include "daemon.h"
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>

関数およびローカル変数の宣言宣言

/*
 * Daemonize the process
 */

int
daemonize(void)
{
    /* Saved sigaction  */
    struct sigaction saved_sa;
    /* Sigaction for signal configuration */
    struct sigaction sa;
    /* File descriptor of /dev/null */
    int fd;
    /* Group ID */
    pid_t newgrp;
    /* Saved errno */
    int saved_errno;
    /* Value returned by sigaction */
    int saved_sa_flag;

SIGHUPの無視

    /* A SIGHUP may be thrown when the parent exits below according to the
       FreeBSD's daemon(3) implementation. */

    sigemptyset(&sa.sa_mask);
    sa.sa_handler = SIG_IGN;
    sa.sa_flags = 0;
    saved_sa_flag = sigaction(SIGHUP, &sa, &saved_sa);

SIGHUP は制御端末のハングアップが発生した場合にセッションリーダに送られ,またセッションリーダが終了した場合にはその制御端末のフォアグラウンドプロセスグループに属する全プロセスに送られる. よって,次の fork システムコール後,親プロセスが終了する際に SIGHUP が発生する可能性がある. そのため,ここで SIGHUP シグナルを無視するように設定する.

プロセスを端末から切り離す

    /* Fork and check the return value */
    switch ( fork() ) {
    case -1:
        /* Error */
        return -1;
    case 0:
        /* Continue if child */
        break;
    default:
        /* Exit if parent */
        exit(0);
    }

    /* Create new session and become the process leader.  N.B., To create new
       session, the process MUST NOT be a process leader. */

    newgrp = setsid();
    /* Save errno */
    saved_errno = errno;

    /* Check the returned value of setsid(); newgrp should same as getpid() */
    if ( -1 == newgrp ) {
        /* Setsid() failed... */
        errno = saved_errno;
        return -1;
    }

まず,このブロックで行っていることを説明します.

  1. fork(2) システムコールを使用して子プロセスを生成し,親プロセスを終了する. これにより,生成された子プロセスはプロセスグループリーダではないことが保証される.
  2. setsid(2) システムコールを使用して新しいセッションを生成し,このセッションリーダとなる. また,このプロセスは新しいプロセスグループ (ID=newgrp) のプロセスグループリーダとなる. ここで生成されたセッションは制御端末 (tty) を持たない.
  3. setsid(2) システムコールの返り値を検査し,正常に新しいセッションが生成できたことを確認する.

ここで注意したいのは, 2. の setsid(2) は,呼び出すプロセスがプロセスグループリーダであるときは失敗することである. つまり, 1. の fork(2) は,バックグラウンドプロセスにするためだけでなく, setsid(2) を呼び出すプロセスがプロセスグループリーダではないことを保証するためでもある.

制御端末を獲得しないようにセッションリーダではなくなる

    /* Fork again to make sure that the process must not have tty, and then
       check the return value. */

    switch ( fork() ) {
    case -1:
        /* Error */
        return -1;
    case 0:
        /* Continue if child */
        break;
    default:
        /* Exit if parent */
        exit(0);
    }

セッションリーダであるプロセスは制御端末を持つ可能性があることを表している. (詳しくは termios(4) に記載されている.) デーモン化されたプロセスが制御端末を持たないようにするため,再度 fork(2) を実行し,セッションリーダではない子プロセスをデーモンプロセスとする.

シグナルハンドラを元に戻す

    /* Recover signal handlers */
    if ( -1 != saved_sa_flag ) {
        /* Succeeded in sigaction, then recover the SIGHUP handling. */
        sigaction(SIGHUP, &saved_saNULL);
    }

カレントワーキングディレクトリを変更する

    /* Change directory if desired. */
    (void)chdir("/");

ここでは,カレントワーキングディレクトリをシステムルート ("/") に変更しているが,デーモンの実行に必要不可欠なディレクトリに変更しても良い. 重要なことは,カレントワーキングディレクトリを変更しない場合,このファイルシステムを(デーモンプロセスを止めずに)アンマウントできなくなることである.

ファイル作成モードをリセットする

    /* Umask */
    (void)umask((mode_t)0);

デーモンプロセスを起動した環境を引き継ぐのであれば,この手順は省略できる.

標準ストリームを /dev/null にリダイレクトする

    if ( -1 != (fd = open(_PATH_DEVNULLO_RDWR0)) ) {
        /* Close stdin, stdout and stderr if desired */
        (void)dup2(fdSTDIN_FILENO);
        (void)dup2(fdSTDOUT_FILENO);
        (void)dup2(fdSTDERR_FILENO);
        if ( fd > 2 ) {
            /* If the /dev/null is not a special descriptor, then close it. */
            (void)close(fd);
        }
    }

dup2(2) システムコールにより標準ストリームを /dev/null にリダイレクトする. 標準ストリームのリダイレクト先が別のファイルディスクリプタに既に設定されている可能性がある場合は,前述した通りクローズした後に再度オープンすることが望ましいが,ここではそれを省略している. また,他のファイルディスクリプタに関してもクローズ操作を省略している.

デーモン化完了

    return 0;
}

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: sw=4 ts=4 fdm=marker
 * vim<600: sw=4 ts=4
 */

参考図書