読者です 読者をやめる 読者になる 読者になる

C言語でprefork型のデーモンを書く(3): デーモン化

C CentOS

prefork して、シグナルで綺麗に終了できるようになったので次はデーモン化する。デーモンをkill しやすいように プロセスIDをファイルに書いておくwrite_pid()関数と、デーモン化関数daemonize()を追加している。

デーモン化の処理が入ったバージョンのソース

#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