HTTPServerのデーモンプロセス化

HTTPServerはクライアントからのリクエストを待ち受けて処理するタイプのプログラムなので、特定のターミナルに紐付けるのではなく、起動後は独立して動作するデーモンプロセスとする必要がある。

github.com

デーモンプロセスにする場合、以下のような手順で行う。

デーモンプロセス関数の実装

main関数の最初にデーモンプロセス関数を呼び出す。

void
daemon_init(const char *pname, const char *root_dir, int facility)
{
	int i;
	pid_t pid;
	if ( (pid = fork() ) != 0)
		exit(0); /* 親プロセスの終了 */
	/* 最初の子プロセス */
	setsid();
	Signal(SIGHUP, SIG_IGN);
	if ( (pid = fork() ) != 0)
		exit(0); /* 最初の子プロセスの終了 */
	/* 番目の子プロセス */	
        daemon_proc = 1; /* err_XXX() 関数用 */
	chdir(root_dir); /* ワーキングディレクトリの変更 */
	umask(0); /* ファイルモード作成マスクのクリア */
	for ( i = 0; i < MAXFD; i++)
		close(i);
	make_pid_file();
	openlog(pname, LOG_PID, facility);
}

まず、fork関数を呼び出して子プロセスを生成する。
fork関数は子プロセスの場合0を返すため、これを利用して親プロセスを終了する。
親プロセスが終了することで、シェルはコマンドの実行が終了したとみなすため、子プロセスはバックグラウンドでの実行に移行する。

この子プロセスはsetsid関数により、グループリーダー、セッションリーダーとなり、コントロールターミナルを持たない状態となる。
この後再度forkを実行してこの子プロセスは終了するが、この時に2番目の子プロセスにSIGHUPが送られるため、これを無視するようSIgnal(SIGHUP, SIG_IGN)を設定しておく。

2回目のforkを実行して最初の子プロセスを終了する。このforkはセッションリーダーではない2番目の子プロセスに処理を引き継ぐことで、将来ターミナルデバイスがオープンされることを防止するために行う。

daemon_procの設定は、エラー関数内でエラーの出力先を標準出力からsyslogに切り替えるためのもの。

chdirではワーキングディレクトリをroot_dirに変更する。このroot_dirは起動スクリプトから実行時引数として受け取ったものである。特に開発中は環境によって実行パスが異なることから、どこのディレクトリで実行しても動くように設定している。
本来はシステムのルートディレクトリをワーキングディレクトリにすることで、実行ディレクトリのファイルシステムがアンマウントできなくなることを防ぐものらしい。

pidファイルの作成

daemon_init関数の最後でmake_pid_file関数を呼び出し、pidファイルを作成している。
デーモンプロセスでは多重起動を防ぐために、このpidファイルが利用される。pidファイルがある場合は起動スクリプトでエラーが返るようになっている。

また、デーモンプロセスは通常プロセスのように簡単に終了することができない。psコマンドでプロセスIDを調べて、kill -TERMなどで終了する手順が必要だが、コードを変更してコンパイルするたびにプロセスの終了起動まで行う必要があり、これが大変面倒である。
pidファイルにpidを書き込むようにしておけば、停止スクリプトでこのファイルを読み込み、記載されているpidのプロセスを終了させることができるため、非常に便利になる。

void
make_pid_file()
{
	FILE *fp;
	char pid_file[512] = "run/server.pid";
	int openerror;
        openerror = errno;
	if ( (fp = fopen(&pid_file[0], "w") ) == NULL) {
		syslog(LOG_NOTICE, "pid file open error\nerror code: %i\nerror message: %s\nfile path: %s\n", openerror, strerror(openerror), pid_file);
		exit(1);
	}
	fprintf(fp, "%i", getpid());
	fclose(fp);
}

実装内容は単純で、ファイルを書き込み用にオープンし、getpid関数で取得したpidを書き込んでいる。
ファイルオープンに失敗した時、errnoにエラーコードが設定されるため、このコードとstrerrorで取得したエラーメッセージをsyslogdに出力している。

制御用スクリプト

前述の通り、デーモンプロセスはプロセスの制御が面倒なため、制御用スクリプトを実装する。

#!/bin/sh

start() {
	echo "Starting server"
    if [ -e ${PIDFILE} ]; then
        echo "${PIDFILE} already exists"
        exit 1
    fi
	mkdir -p "$RUNDIR"
	../build/server $ROOTDIR
}

stop() {
	echo "Stopping server"
	readonly PID=`cat ${PIDFILE}`
	kill -TERM $PID
	rm -rf "$RUNDIR"
}

readonly USER=`whoami`
if [ "$USER" != "root" ]; then
	echo "Alert: Run as root"
	exit 1
fi

if [ $# -ne 1 ]; then
	echo "Alert: Run with argument as start, stop, or restart"
	exit 1
fi

cd `dirname $0` || exit 1
readonly ROOTDIR=`pwd`"/../"
readonly RUNDIR=${ROOTDIR}"run/"
readonly PIDFILE=${RUNDIR}"server.pid"

case $1 in
	start)
		start
		;;
	stop)
		stop
		;;
	restart)
		stop
		start
		;;
	*)
	echo "Alert: Run with argument as start, stop, or restart"
		;;
esac

実行時引数にstart, stop, restartを取り、これによって処理を分けている。
また、HTTPServerはport80で動作させているため、root権限を要求する。

start処理では、多重起動防止のためにpidファイルの存在チェックを行い、このファイルが存在する場合にはエラーで終了させる。
また、pidファイルの配置場所となるrunディレクトリを作成している。

stop処理では、pidファイルの中身を読み、この番号によって、実行しているデーモンプロセスを終了させている。
またrunディレクトリごとpidファイルを削除する。

restart処理では、stop -> start を逐次実行している。

参考書籍:

UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI

UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI

  • 作者: W.リチャードスティーヴンス,W.Richard Stevens,篠田陽一
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 1999/07
  • メディア: 単行本
  • 購入: 8人 クリック: 151回
  • この商品を含むブログ (37件) を見る