C 言語で SIGINT シグナルを処理する

胡金庫 2023年10月12日
  1. signal 関数を使用して SIGINT シグナルハンドラルーチンを登録する
  2. sigaction 関数を使用して SIGINT シグナルハンドラルーチンを登録する
C 言語で SIGINT シグナルを処理する

この記事では、C 言語で SIGINT シグナルを処理する方法に関する複数の方法を示します。

signal 関数を使用して SIGINT シグナルハンドラルーチンを登録する

SIGINT は、端末の割り込み文字(通常はCtrl + C)に関連付けられている事前定義された信号の 1つです。これにより、シェルは現在のプロセスを停止してメインループに戻り、ユーザーに新しいコマンドプロンプトを表示します。シグナルは、カーネル内のプロセスとユーザースペースの間で送信される単なる小さな通知であることに注意してください。これらは通常、プログラムの通常の実行を停止し、特定の信号タイプに対して特別なアクションを実行するため、ソフトウェア割り込みと呼ばれることもあります。アクションはほとんどの場合、システム全体のデフォルトとして定義されていますが、ユーザーは特別な機能を実装して、それをシグナルの新しいアクションとして登録できます。

ただし、一部のシグナルはオペレーティングシステムから割り当てられた厳密に固定された動作を持ち、カーネルがそれらを使用して応答しないプロセスを終了するなどの重要なことを行うため、オーバーライドできません。

ただし、SIGINT は処理可能なシグナルの一種です。つまり、ユーザーは、プロセスがシグナルを受信したときに実行されるカスタム関数を登録できます。SIGINT シグナルのデフォルトのアクションは、プロセスを終了することです。次のサンプルコードでは、無限の while ループを実行するプログラムを実装し、その中で fprintf 関数を継続的に呼び出します。ただし、ループが開始される直前に、signal 関数を呼び出して、SIGINT sigintHandler 関数をハンドラーとして登録します。このハンドラーは、関数呼び出しが 1つだけで、文字列を stdout に出力します。

シグナルハンドラコードは、内部のグローバルプログラムデータを変更する非再入可能関数を呼び出してはならないため、printf の代わりに write が使用されることに注意してください。例を示すために、プログラムを実行してから、他の端末から SIGINT シグナルを送信して動作を観察する必要があります。通常、while ループの実行を停止し、"Caught SIGINT!"文字列を出力して、成功ステータスコードで終了します。

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

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

static void sigintHandler(int sig) {
  write(STDERR_FILENO, "Caught SIGINT!\n", 15);
}

int main(int argc, char *argv[]) {
  if (signal(SIGINT, sigintHandler) == SIG_ERR) errExit("signal SIGINT");

  while (1) {
    fprintf(stderr, "%d", 0);
    sleep(3);
  }

  exit(EXIT_SUCCESS);
}

sigaction 関数を使用して SIGINT シグナルハンドラルーチンを登録する

UNIX システムでの signal 関数呼び出しの最新の実装は、単純なユースケースでは確実に機能しますが、シグナルハンドラーの登録には sigaction 関数を使用することをお勧めします。signal 呼び出しと比較してはるかに多くのオプションを提供しますが、信号の深刻なユースケースに必要なコア機能も提供します。sigaction は、特別な struct sigaction 引数を取り、ハンドラー関数ポインターおよびその他のインジケーターを指定します。この場合、子プロセスが、親が待機している間に、条件式としてグローバル変数 shutdown_flag を使用して while ループを実行するシナリオを実装します。shutdown_flag 変数はタイプ sig_atomic_t であることに注意してください。これは、シグナルハンドラコードからグローバルに安全に変更できる特別な整数です。したがって、ユーザーが子プロセスに SIGINT シグナルを送信すると、cleanupRoutine 関数が呼び出され、shutdown_flag0 値に設定され、コントロールが while ループに戻り、条件式が再度評価されます。ゼロはそれをループから切り離すことを強制します。waitpid 関数が戻ると、子は終了し、親はそのステータスを取得します。

#define _POSIX_C_SOURCE 199309
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

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

volatile sig_atomic_t shutdown_flag = 1;

void cleanupRoutine(int signal_number) { shutdown_flag = 0; }

int main(void) {
  int wstatus;

  pid_t c_pid = fork();
  if (c_pid == -1) errExit("fork");

  if (c_pid == 0) {
    printf("printed from child process - %d\n", getpid());

    int count = 0;
    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 SIGINT handler
    if (sigfillset(&sigterm_action.sa_mask) != 0) errExit("sigfillset");

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

    while (shutdown_flag) {
      getpid();
    }
    printf("pid: %d exited\n", getpid());

    exit(EXIT_SUCCESS);
  } else {
    printf("printed from parent process - %d\n", getpid());
    int ret;

    if (waitpid(c_pid, &wstatus, WUNTRACED) == -1) errExit("waitpid");
  }

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

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

LinkedIn Facebook

関連記事 - C Signal