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

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

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

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

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

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

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.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);
}

関連記事 - C Process

  • C 言語で sched_setaffinity 関数を使用する