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