C 言語で他のプロセスからデーモンプロセスを制御する

胡金庫 2023年10月12日
  1. fork および setsid 関数を使用してデーモンプロセスを作成する
  2. daemon 関数を使用してデーモンプロセスを作成する
C 言語で他のプロセスからデーモンプロセスを制御する

この記事では、C 言語で他のプロセスからデーモンプロセスを制御する方法に関する複数の方法を紹介します。

fork および setsid 関数を使用してデーモンプロセスを作成する

デーモンプロセスには、長時間実行されるプロセスであるなど、いくつかの特徴があり、デーモンはシステムの起動時に開始される場合があります。デーモンプロセスは、起動時に強制的に終了または一時停止、さらには無効にするユーザーコマンドから制御できます。ただし、一般的なシナリオでは、いくつかのシステム固有のスクリプトを使用して、システムのシャットダウン時にデーモンが終了する必要があります。デーモンは通常、制御端末なしでバックグラウンドで実行されます。このような機能を使用するプログラムは、外部割り込みを処理するためにさまざまなシグナルハンドラーを実装する必要があります。

デーモンプロセスを作成して監視する方法は複数ありますが、この例では、fork 関数を呼び出して子プロセスを作成する作成段階を示します。次に、親プロセスが終了し、子が init プロセスの子になると実行を続行します(Linux システムでは、init プロセスが起動時の最初のプロセスです)。子プロセスは setsid 関数を呼び出して新しいセッションを開始し、制御端末からプロセスを削除します。最後に、もう一度 fork を呼び出し、親プロセスを終了して、デーモンが制御端末を取得しないようにします。現在、デーモンプロセスを実行しています。システムまたはユーザーが対応する割り込みを配信したときに、リソースのクリーンアップと正常な終了を実行するために、SIGTERM シグナルハンドラーを登録することが重要です。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define errExit(msg)    \
  do {                  \
    perror(msg);        \
    exit(EXIT_FAILURE); \
  } while (0)

void cleanupRoutine(int signal_number) {
  write(STDERR_FILENO, "hello", 5);
  _exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[]) {
  fprintf(stderr, "[pid - %d] running...\n", getpid());

  switch (fork()) {
    case -1:
      errExit("fork");
    case 0:
      break;
    default:
      _exit(EXIT_SUCCESS);
  }
  fprintf(stderr, "[pid - %d] running...\n", getpid());

  if (setsid() == -1) errExit("setsid");

  switch (fork()) {
    case -1:
      errExit("fork");
    case 0:
      break;
    default:
      _exit(EXIT_SUCCESS);
  }
  fprintf(stderr, "[pid - %d] running...\n", getpid());

  struct sigaction sigterm_action;

  memset(&sigterm_action, 0, sizeof(sigterm_action));
  sigterm_action.sa_handler = &cleanupRoutine;
  sigterm_action.sa_flags = 0;

  // Mask other signals from interrupting SIGTERM handler
  if (sigfillset(&sigterm_action.sa_mask) != 0) {
    perror("sigfillset");
    exit(EXIT_FAILURE);
  }
  // Register SIGTERM handler
  if (sigaction(SIGTERM, &sigterm_action, NULL) != 0) {
    perror("sigaction SIGTERM");
    exit(EXIT_FAILURE);
  }

  while (1) {
    getpid();
  }

  exit(EXIT_SUCCESS);
}

daemon 関数を使用してデーモンプロセスを作成する

前の例は一見正しいコードを示していますが、親から継承されたすべての開いているファイル記述子を確実に閉じるための手順がありません。作業ディレクトリが変更され、標準入力が/dev/null にリダイレクトされるなど。これらの手順は、デーモンがそれらを呼び出した場合に特定の関数が失敗しないことを保証し、またいくつかの奇妙な動作が観察されないことを保証します。

たとえば、ターミナルウィンドウから前のプログラムを起動し、SIGTERM シグナルをデーモンプロセスに送信した場合、cleanupRoutine シグナルハンドラーの write 関数は、新しいプロンプトが表示された後でも同じターミナルに出力されます。表示されます。したがって、デーモン関数は GNUC ライブラリによって提供されます。上記の手順を自動的に処理して、新しく作成されたデーモンプロセスのクリーンなコンテキストを確保します。daemon 関数は 2つの整数引数を取ります。最初の引数(ゼロに等しい場合)は、現在の作業ディレクトリをルートディレクトリに変更する必要があるかどうかを指定します。標準 I/O ストリームを/dev/null にリダイレクトする必要があるかどうかを示す 2 番目の整数(ゼロに等しい場合)。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define errExit(msg)    \
  do {                  \
    perror(msg);        \
    exit(EXIT_FAILURE); \
  } while (0)

void cleanupRoutine(int signal_number) {
  write(STDERR_FILENO, "hello", 5);
  _exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[]) {
  fprintf(stderr, "[pid - %d] running...\n", getpid());

  if (daemon(0, 0) == -1) errExit("daemon");

  fprintf(stderr, "[pid - %d] running...\n", getpid());

  struct sigaction sigterm_action;

  memset(&sigterm_action, 0, sizeof(sigterm_action));
  sigterm_action.sa_handler = &cleanupRoutine;
  sigterm_action.sa_flags = 0;

  // Mask other signals from interrupting SIGTERM handler
  if (sigfillset(&sigterm_action.sa_mask) != 0) errExit("sigfillset");

  // Register SIGTERM handler
  if (sigaction(SIGTERM, &sigterm_action, NULL) != 0) errExit("sigaction");

  while (1) {
    getpid();
  }

  exit(EXIT_SUCCESS);
}
著者: 胡金庫
胡金庫 avatar 胡金庫 avatar

DelftStack.comの創設者です。Jinku はロボティクスと自動車産業で8年以上働いています。自動テスト、リモートサーバーからのデータ収集、耐久テストからのレポート作成が必要となったとき、彼はコーディングスキルを磨きました。彼は電気/電子工学のバックグラウンドを持っていますが、組み込みエレクトロニクス、組み込みプログラミング、フロントエンド/バックエンドプログラミングへの関心を広げています。

LinkedIn Facebook

関連記事 - C Process