Linuxにおけるデーモンプロセスの実装とログ管理の実践

本記事では、Linux環境においてC言語を用いてデーモンプロセスを作成し、定期的にシステムログへタイムスタンプを記録する実装手法について解説します。デーモンはバックグラウンドで動作し、ユーザーの介入なしに特定のタスクを実行する長寿命のプロセスです。

デーモンプログラムの実装

以下のコードは、標準的なデーモンの生成手順(フォーク、セッションリーダーへの昇格、標準入出力の切断)を踏まえた実装例です。このプログラムは /var/log/ ディレクトリ配下にログファイルを作成し、30秒おきに現在時刻を書き込みます。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>

int main(void) {
    pid_t process_id;
    FILE *log_stream;
    time_t current_epoch;
    struct tm *time_struct;
    char time_buffer[80];

    // ログファイルのオープン(追記モード)
    log_stream = fopen("/var/log/demo_daemon.log", "a");
    if (log_stream == NULL) {
        perror("ログファイルを開けませんでした");
        exit(EXIT_FAILURE);
    }
    // 行バッファリングを有効にする
    setlinebuf(log_stream);

    // 子プロセスの生成
    process_id = fork();
    
    // フォーク失敗時の処理
    if (process_id < 0) {
        perror("フォークに失敗しました");
        fclose(log_stream);
        exit(EXIT_FAILURE);
    }

    // 親プロセスの終了(子プロセスをバックグラウンドに移行)
    if (process_id > 0) {
        printf("デーモン起動 (PID: %d)\n", process_id);
        exit(EXIT_SUCCESS);
    }

    // 新しいセッションの作成(制御端末からの切り離し)
    if (setsid() < 0) {
        perror("セッションの作成に失敗しました");
        fclose(log_stream);
        exit(EXIT_FAILURE);
    }

    // 標準入力、標準出力、標準エラー出力のクローズ
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    // デーモンのメインループ
    while (1) {
        sleep(30); // 30秒間待機

        // 現在時刻の取得とフォーマット
        time(&current_epoch);
        time_struct = localtime(&current_epoch);
        strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S", time_struct);

        // ログへの書き込み
        fprintf(log_stream, "[DAEMON] System status check at %s\n", time_buffer);
        fflush(log_stream); // バッファの強制書き出し
    }

    fclose(log_stream);
    return 0;
}

コンパイルと実行手順

ログファイルの作成先である /var/log/ への書き込みにはroot権限が必要です。以下の手順でコンパイルし、実行します。

gcc daemon.c -o daemon_app
sudo ./daemon_app

実行すると、親プロセスが終了し、子プロセスがバックグラウンドで動作を続けます。sudo を使用しない場合、権限エラーによりログファイルの作成に失敗します。

動作確認

ログファイルが正しく作成され、記録されているかを確認します。まず、ファイルの存在をチェックします。

ls -l /var/log/demo_daemon.log

次に、tail コマンドを使用してリアルタイムでログの出力を監視します。プログラム内の sleep(30) に従い、30秒ごとに新しい行が追加されることが確認できます。

tail -f /var/log/demo_daemon.log

プロセスの終了

バックグラウンドで動作しているデーモンプロセスを停止するには、プロセスID (PID) を特定して kill コマンドを実行します。

ps aux | grep daemon_app
sudo kill [PID]

正しく終了すると、それ以上ログが書き込まれなくなります。

主要システムコールと関数の解説

本実装で使用した重要な関数とその役割について説明します。

1. fork()

現在のプロセスを複製し、新しい子プロセスを作成します。親プロセスには子プロセスのPIDを、子プロセスには0を返します。デーモンを作成する際、親プロセスを終了させることで、子プロセスを制御端末から独立させてバックグラウンドで実行させます。

2. setsid()

新しいセッションを作成し、呼び出しプロセスをそのセッションリーダーにします。これにより、プロセスは制御端末(TTY)を持たなくなります。デーモンがログアウト後も動作し続けるために必須の処理です。

3. close()

ファイルディスクリプタを閉じます。デーモンは端末とやり取りしないため、標準入力(0)、標準出力(1)、標準エラー出力(2)を閉じてリソースを解放し、意図しない入出力を防ぎます。

4. fopen() / fprintf() / fflush()

fopenでファイルストリームを開き、fprintfでフォーマットされた文字列を書き込みます。fflushはバッファに滞留しているデータを即座にディスクに書き込むために使用します。デーモンが異常終了した場合でもログが残るよう、定期的なフラッシュは重要です。

5. time() / localtime() / strftime()

timeは現在のエポック秒を取得し、localtimeはそれをローカル時刻の構造体に変換します。strftimeは構造体を元に任意の書式で文字列化します。これにより、人間が読みやすい日時形式でログを記録できます。

6. sleep()

プロセスの実行を指定した秒数だけ一時停止します。デーモンが継続的にCPUリソースを消費しないよう、定期的なタスクの間隔を制御するために使用されます。

タグ: linux C SystemProgramming Daemon OS

5月15日 04:27 投稿