C++ で SIGABRT シグナルを処理する

胡金庫 2023年10月12日
  1. シグナルハンドラ SIGABRT を登録するには sigaction を使用する
  2. シグナルハンドラの変数 sig_atomic_t を利用する
C++ で SIGABRT シグナルを処理する

この記事では、C++ で SIGABRT 信号を処理する方法の複数の方法を示します。

シグナルハンドラ SIGABRT を登録するには sigaction を使用する

Unix ベースのオペレーティングシステムは signal と呼ばれる機能をサポートしています。これは、あるプログラムが非同期的に別のプログラムにメッセージを送ることができるメカニズムです。シグナルは通常、ユーザやプログラムを中断させる必要のあるオペレーティングシステムのプロセスから送られます。シグナルハンドラは、プログラムが与えられたシグナルを受信した場合に呼び出されるコードセクションです。プログラムの中止、プログラムの停止、プログラムの継続などのタスクには、対応するデフォルトのシグナルハンドラを持ついくつかの標準的なシグナルがあります。しかし、ユーザはこれらのシグナルハンドラのほとんどをカスタム定義の関数でオーバーライドすることができ、sigaction はそれを登録する関数です。

SIGABRT はデフォルトでプログラムを終了させるシグナルの一つであることに注意してください。次のコード例では、シグナルハンドラをオーバーライドし、シグナルを受信したら呼び出されるように定義した関数(cleanupRoutine)を割り当てる。

まず、struct sigaction 型のオブジェクトを宣言し、memset 関数呼び出しで初期化します。

次に、そのデータメンバ sa_handler に呼び出したい関数のアドレスを代入します。

この後、他のシグナルが SIGABRT ハンドラに割り込まれないようにマスクする必要があるが、sigfillset ファンクションコールでこれを実現します。

最後に、シグナルハンドラを sigaction コールで登録します。このコールはシグナル番号と struct sigaction のアドレス、そして前回のアクションを保存できるオプションの構造体の 3つの引数をとります。

この場合、3 番目の引数は無視しますが、パラメータとして nullptr を指定する必要があります。関数 cleanupRoutine を定義して、コンソールに文字列を表示してからプログラムを終了し、ハンドラが正しく実行されたかどうかを簡単に確認できるようにした。プログラムの残りの部分は、ユーザが SIGABRT シグナルを送ったときに中断する必要がある無限ループです。プログラムをテストするには、次のコマンド kill -SIGABRT pid_num_of_program を実行して、1つのターミナルウィンドウでプログラムを実行し、2つ目のウィンドウからシグナルを送信します。

#include <csignal>
#include <cstring>
#include <iostream>

void cleanupRoutine(int signal_number) {
  write(2, "printed from cleanupRoutine\n", 28);
  _exit(EXIT_SUCCESS);
}

int main() {
  struct sigaction sigabrt_action {};
  memset(&sigabrt_action, 0, sizeof(sigabrt_action));
  sigabrt_action.sa_handler = &cleanupRoutine;

  if (sigfillset(&sigabrt_action.sa_mask) != 0) {
    perror("sigfillset");
    exit(EXIT_FAILURE);
  }
  if (sigaction(SIGABRT, &sigabrt_action, nullptr) != 0) {
    perror("sigaction SIGABRT");
    exit(EXIT_FAILURE);
  }

  int i = 0;
  while (true) {
    i += 1;
  }

  exit(EXIT_SUCCESS);
}

シグナルハンドラの変数 sig_atomic_t を利用する

シグナルハンドラは、特定の機能を持つことが要求される特殊なタイプの関数です。すなわち、正しく操作できることが保証されている変数は原子変数のみです。シグナルハンドラの実行中に設定できる整数である sig_atomic_t という特殊な型があります。この変数は volatile キーワードで宣言されており、変更があった場合はグローバルに見られます。次の例は、この変数をループ文の条件としてどのように含めるかを示しています。

ハンドラは変数を 1 から 0 に設定するだけで、シグナルを受信した場合にはプログラムを終了しないことに注意してください。つまり、プログラムは中断された地点から継続しようとします。この場合、ループの繰り返しが再開され、一旦条件が偽であることが確認されるとループから飛び出します。このようにして、信号を利用してプログラムの動作を制御することができます。

#include <csignal>
#include <cstring>
#include <iostream>

using std::cout;
using std::endl;

volatile sig_atomic_t shutdown_flag = 1;

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

int main() {
  struct sigaction sigabrt_action {};
  memset(&sigabrt_action, 0, sizeof(sigabrt_action));
  sigabrt_action.sa_handler = &cleanupRoutine;

  if (sigfillset(&sigabrt_action.sa_mask) != 0) {
    perror("sigfillset");
    exit(EXIT_FAILURE);
  }
  if (sigaction(SIGABRT, &sigabrt_action, nullptr) != 0) {
    perror("sigaction SIGABRT");
    exit(EXIT_FAILURE);
  }

  int i = 0;
  while (shutdown_flag) {
    i += 1;
  }
  cout << "Exiting ..." << endl;

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

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

LinkedIn Facebook