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_flag が 0 値に設定され、コントロールが 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);
}
