C言語でprefork型のデーモンを書く(3): デーモン化
prefork して、シグナルで綺麗に終了できるようになったので次はデーモン化する。デーモンをkill しやすいように プロセスIDをファイルに書いておくwrite_pid()関数と、デーモン化関数daemonize()を追加している。
- my_prefork_daemon.c
デーモン化の処理が入ったバージョンのソース
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <apr_hash.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <sys/wait.h> #include <signal.h> #define MAX_CHILDREN 16 //子プロセスの数 #define PID_FILE "/var/run/my_prefork_daemon.pid" void kill_all_children(int); void signal_handler(int); void write_pid(); void daemonize(); // 子プロセスの管理にハッシュテーブルを使う static apr_pool_t* pool; static apr_hash_t* hChildren; int main(int argc, char **argv){ // デーモン化 daemonize(); //kill しやすいように pid ファイルの作成 write_pid(); // SIGTERM ですべての子プロセスを殺すようにシグナルハンドラを設定 signal(SIGTERM, kill_all_children); // ハッシュの初期化 apr_initialize(); apr_pool_create(&pool, NULL); hChildren = apr_hash_make(pool); //親プロセスのループ while(1){ while( apr_hash_count(hChildren) >= MAX_CHILDREN ){ int status; pid_t child_pid = wait( &status ); //子プロセスが死ぬまで待つ apr_hash_set(hChildren, &child_pid, sizeof(child_pid), NULL); //死んだ子プロセスをハッシュテーブルから削除 } pid_t *pid = apr_palloc(pool, sizeof(pid_t)); *pid = fork(); //フォーク if(*pid==0){ signal(SIGTERM, signal_handler); goto CHILDREN; //子プロセスだったら、ループから抜ける } apr_hash_set(hChildren, pid, sizeof(pid_t), 1); //子プロセスをハッシュテーブルに追加 usleep(100); } CHILDREN: while(1){ //子プロセスの処理をここに書く sleep(1); } } void kill_all_children(int sig){ apr_hash_index_t *hi; apr_ssize_t klen; pid_t *child_pid_ptr; int val; for( hi=apr_hash_first(pool, hChildren); hi; hi=apr_hash_next(hi) ){ apr_hash_this(hi, &child_pid_ptr, &klen, &val); int ret = kill(*child_pid_ptr, SIGTERM); } exit(0); } void signal_handler(int sig){ exit(0); } void write_pid(){ FILE *fp; fp = fopen(PID_FILE, "w"); fprintf(fp, "%d", getpid()); fclose(fp); } void daemonize(){ pid_t pid, sid; /* カレントディレクトリを / に変更 */ if( chdir("/") < 0 ) exit(EXIT_FAILURE); /* 子プロセスの開始 */ pid = fork(); if( pid < 0 ) exit(EXIT_FAILURE); /* 親プロセスを終了 */ if( pid > 0 ) exit(EXIT_SUCCESS); /* 新しいセッションを作成 */ sid = setsid(); if( sid < 0 ) exit(EXIT_FAILURE); /* ファイル作成マスクをリセット */ umask(0); /* 標準入力,標準出力,標準エラー出力を閉じる */ close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); return; }
細かいところはいい加減なので、警告がやたらでるので本番で使うときは -Wall を付けて警告がなくなるまで要修正。
# gcc -g -I /usr/include/apr-1 -L/usr/lib/apr-1 -lapr-1 my_prefork_daemon.c -o my_prefork_daemon
- 実行
デーモン化されたため実行するとすぐにプロンプトが返ってくる。また、psしてみると、親プロセスの親プロセスIDが、シェルのプロセスIDではなく、「1」(initのプロセスID)になっているはず。
# ./my_prefork_daemon # ps -ef|grep my_prefork_daemon root 8529 1 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8530 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8531 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8532 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8533 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8534 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8535 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8536 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8537 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8538 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8539 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8540 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8541 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8542 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8543 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8544 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon root 8545 8529 0 23:40 ? 00:00:00 ./my_prefork_daemon 503 8547 4614 0 23:41 pts/3 00:00:00 grep my_prefork_daemon
- プロセスの終了方法
親プロセスに対してシグナルを送信すれば終了できる。デーモンらしくプロセスIDを /var/run/my_prefork_daemon.pid というファイル名で残すようにしたので、シグナルの送信方法は、以下のようにすればOK。今後、/etc/init.d/ の下に起動/終了スクリプトを書くときにも使える。
# kill `cat /var/run/my_prefork_daemon.pid`
参考
C言語でprefork型のデーモンを書く(1): 非デーモン prefork サンプル - Sleepless geek in Seattle
C言語でprefork型のデーモンを書く(2): 非デーモン prefork シグナルハンドラ付き - Sleepless geek in Seattle