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