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);
}