初心者向けC言語signal.hライブラリの使い方ガイド

C言語のsignal.hライブラリは、プログラムが実行中に受け取るシグナルを処理するための強力なツールです。シグナルは、特定のイベントやエラーが発生したときにプログラムに通知を送るために使用されます。例えば、Ctrl+Cを押すとプログラムが中断されるのもシグナルの一例です。この記事では、signal.hライブラリの基本的な使い方から応用例までを詳しく解説し、シグナル処理の理解を深めるための演習問題も提供します。

目次

signal.hライブラリとは

signal.hライブラリは、C言語でシグナル処理を行うための標準ライブラリです。シグナルとは、プログラムに特定のイベントが発生したことを通知するための仕組みで、例えば異常終了や外部からの中断要求などがあります。このライブラリを使用することで、プログラムが特定のシグナルを受け取った際に適切な処理を行うことができます。signal.hライブラリを活用することで、エラー処理やユーザーの中断操作に対する対応が柔軟に行えます。

シグナルの基本概念

シグナルは、オペレーティングシステムがプロセスに対して送る通知メカニズムです。これは、特定のイベントが発生したときにプログラムに通知を送るために使用されます。シグナルには、異常終了(例えばゼロ除算)、外部からの中断要求(Ctrl+Cによる中断)、タイマーの満了などがあります。シグナルを受け取ったプログラムは、事前に設定したシグナルハンドラ関数を呼び出すことで適切な対応を行うことができます。シグナル処理を正しく設定することで、プログラムの堅牢性と信頼性を高めることができます。

signal.hの基本関数

signal.hライブラリには、シグナルの送受信やシグナルハンドラの設定を行うための基本的な関数が含まれています。ここでは、いくつかの重要な関数について紹介します。

signal()関数

signal()関数は、特定のシグナルを受け取ったときに実行されるハンドラ関数を設定します。次のように使用します。

#include <signal.h>

void handler(int signum) {
    // シグナルを受け取ったときの処理
}

int main() {
    signal(SIGINT, handler); // Ctrl+Cで発生するSIGINTシグナルをハンドラに設定
    while (1) {
        // メインループ
    }
    return 0;
}

raise()関数

raise()関数は、プログラム自身がシグナルを送るために使用します。次のように使用します。

#include <signal.h>

int main() {
    raise(SIGINT); // 自分自身にSIGINTシグナルを送信
    return 0;
}

sigaction()関数

sigaction()関数は、より高度なシグナルハンドラの設定を行うために使用されます。これにより、ハンドラの詳細な挙動を制御できます。

#include <signal.h>

void handler(int signum) {
    // シグナルを受け取ったときの処理
}

int main() {
    struct sigaction sa;
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL); // SIGINTシグナルに対してハンドラを設定
    while (1) {
        // メインループ
    }
    return 0;
}

シグナルハンドラの設定

シグナルハンドラは、特定のシグナルを受け取ったときに実行される関数です。これにより、プログラムがシグナルを受け取った際の動作をカスタマイズできます。以下に、シグナルハンドラの設定方法と具体例を示します。

シグナルハンドラの定義

シグナルハンドラは、引数としてシグナル番号を受け取り、void型の関数として定義します。

#include <stdio.h>
#include <signal.h>

void signal_handler(int signum) {
    printf("Received signal %d\n", signum);
}

signal()関数を用いたハンドラの設定

signal()関数を使って、特定のシグナルに対してハンドラを設定します。

int main() {
    signal(SIGINT, signal_handler); // Ctrl+Cで発生するSIGINTシグナルに対してハンドラを設定
    while (1) {
        // メインループ
        sleep(1);
    }
    return 0;
}

この例では、Ctrl+Cを押すとsignal_handlerが呼び出され、シグナル番号が表示されます。

sigaction()関数を用いたハンドラの設定

sigaction()関数を使うことで、より高度なシグナルハンドラの設定が可能です。

int main() {
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL); // SIGINTシグナルに対してハンドラを設定
    while (1) {
        // メインループ
        sleep(1);
    }
    return 0;
}

sigaction()関数を使うことで、複数のシグナルに対して異なるハンドラを設定することが容易になります。

実践例:SIGINTの処理

ここでは、SIGINTシグナル(通常、Ctrl+Cで送信されるシグナル)の処理方法を具体的なコード例を交えて解説します。SIGINTシグナルを受け取ったときに、適切なクリーンアップ処理を行ってプログラムを安全に終了させる方法を紹介します。

SIGINTシグナルハンドラの定義

まず、SIGINTシグナルを受け取ったときの処理を行うハンドラ関数を定義します。

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

void sigint_handler(int signum) {
    printf("Received SIGINT (Ctrl+C). Exiting...\n");
    exit(0); // プログラムを終了
}

signal()関数を用いたSIGINTハンドラの設定

次に、signal()関数を使用してSIGINTシグナルに対するハンドラを設定します。

int main() {
    signal(SIGINT, sigint_handler); // SIGINTシグナルに対してハンドラを設定

    while (1) {
        printf("Running...\n");
        sleep(1); // 1秒間スリープ
    }
    return 0;
}

このコードでは、プログラムが実行中にCtrl+Cを押すと、sigint_handler関数が呼び出され、「Received SIGINT (Ctrl+C). Exiting…」というメッセージが表示されてプログラムが終了します。

sigaction()関数を用いた高度なSIGINTハンドラの設定

sigaction()関数を使って、より高度な設定を行うこともできます。

int main() {
    struct sigaction sa;
    sa.sa_handler = sigint_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL); // SIGINTシグナルに対してハンドラを設定

    while (1) {
        printf("Running...\n");
        sleep(1); // 1秒間スリープ
    }
    return 0;
}

sigaction()関数を使うことで、他のシグナル処理と併用した場合にも柔軟なハンドリングが可能です。

シグナル処理の応用例

シグナル処理は、単純な中断処理以外にも多くの応用が可能です。ここでは、いくつかの高度なシグナル処理の実例を紹介します。

タイマーを使ったシグナル処理

タイマーを使用して、一定時間ごとに特定の処理を実行することができます。以下の例では、タイマーシグナル(SIGALRM)を使って、定期的にメッセージを表示します。

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

void timer_handler(int signum) {
    printf("Timer expired\n");
}

int main() {
    struct sigaction sa;
    sa.sa_handler = timer_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGALRM, &sa, NULL);

    alarm(2); // 2秒後にSIGALRMシグナルを発生

    while (1) {
        pause(); // シグナルを待つ
    }
    return 0;
}

マルチスレッド環境でのシグナル処理

マルチスレッド環境でのシグナル処理には注意が必要です。以下の例では、POSIXスレッドとシグナルを組み合わせて、シグナルがメインスレッドで処理されるようにします。

#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>

void sigint_handler(int signum) {
    printf("Received SIGINT in main thread\n");
}

void* worker_thread(void* arg) {
    while (1) {
        printf("Worker thread running...\n");
        sleep(1);
    }
    return NULL;
}

int main() {
    struct sigaction sa;
    sa.sa_handler = sigint_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL);

    pthread_t thread;
    pthread_create(&thread, NULL, worker_thread, NULL);

    while (1) {
        pause(); // シグナルを待つ
    }

    return 0;
}

この例では、SIGINTシグナルが発生すると、メインスレッドでハンドラが実行されるようになっています。ワーカースレッドはバックグラウンドで動作し続けます。

シグナルのブロックとアンブロック

特定のシグナルを一時的にブロック(無視)し、その後アンブロックすることで、クリティカルセクションの保護などが可能です。

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

void sigint_handler(int signum) {
    printf("SIGINT received\n");
}

int main() {
    struct sigaction sa;
    sa.sa_handler = sigint_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL);

    sigset_t new_set, old_set;
    sigemptyset(&new_set);
    sigaddset(&new_set, SIGINT);

    // シグナルをブロック
    sigprocmask(SIG_BLOCK, &new_set, &old_set);
    printf("SIGINT is blocked for 5 seconds\n");
    sleep(5);

    // シグナルをアンブロック
    sigprocmask(SIG_SETMASK, &old_set, NULL);
    printf("SIGINT is unblocked\n");

    while (1) {
        sleep(1);
    }
    return 0;
}

この例では、SIGINTシグナルを5秒間ブロックし、その後アンブロックしています。

演習問題

この記事で学んだ内容を定着させるために、いくつかの演習問題を用意しました。これらの問題に取り組むことで、signal.hライブラリの使用方法とシグナル処理の実践的なスキルを向上させることができます。

演習1: 基本的なシグナルハンドラの実装

SIGTERMシグナルを受け取ったときに、「プログラムが終了しました」というメッセージを表示してプログラムを終了するシンプルなCプログラムを作成してください。

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

void sigterm_handler(int signum) {
    printf("プログラムが終了しました\n");
    exit(0);
}

int main() {
    signal(SIGTERM, sigterm_handler); // SIGTERMシグナルに対してハンドラを設定

    while (1) {
        printf("プログラム実行中...\n");
        sleep(1);
    }
    return 0;
}

演習2: タイマーシグナルの利用

2秒ごとに「2秒経過しました」というメッセージを表示するCプログラムを作成してください。SIGALRMシグナルを利用してください。

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

void timer_handler(int signum) {
    printf("2秒経過しました\n");
    alarm(2); // 次のアラームを設定
}

int main() {
    struct sigaction sa;
    sa.sa_handler = timer_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGALRM, &sa, NULL);
    alarm(2); // 初回のアラームを設定

    while (1) {
        pause(); // シグナルを待つ
    }
    return 0;
}

演習3: マルチスレッド環境でのシグナル処理

2つのスレッドを作成し、一方のスレッドがSIGUSR1シグナルを送信し、もう一方のスレッドがそのシグナルを受け取ってメッセージを表示するプログラムを作成してください。

#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>

void sigusr1_handler(int signum) {
    printf("SIGUSR1を受信しました\n");
}

void* sender_thread(void* arg) {
    sleep(2);
    pthread_kill(pthread_self(), SIGUSR1);
    return NULL;
}

void* receiver_thread(void* arg) {
    struct sigaction sa;
    sa.sa_handler = sigusr1_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    sigaction(SIGUSR1, &sa, NULL);

    while (1) {
        pause(); // シグナルを待つ
    }
    return NULL;
}

int main() {
    pthread_t sender, receiver;

    pthread_create(&receiver, NULL, receiver_thread, NULL);
    pthread_create(&sender, NULL, sender_thread, NULL);

    pthread_join(sender, NULL);
    pthread_join(receiver, NULL);

    return 0;
}

まとめ

C言語のsignal.hライブラリは、プログラムが実行中に受け取るシグナルを処理するための強力なツールです。この記事では、signal.hライブラリの基本的な使い方から応用例までを詳しく解説し、具体的なコード例を用いてシグナル処理の実装方法を説明しました。シグナルハンドラの設定、タイマーシグナルの利用、マルチスレッド環境でのシグナル処理など、様々なシナリオでのシグナル処理を学ぶことで、プログラムの堅牢性と信頼性を向上させることができます。さらに、演習問題を通じて実践的なスキルを磨き、シグナル処理の理解を深めてください。

コメント

コメントする

目次