シグナルハンドラってなんだかとっつきにくいイメージがありますよね?
しかもsignal()とsgiaction()で2通りのやり方があるし、どっちを使ったらいいのか迷ってしまいます。
この記事では、そんな方のためにsignal()とsigaction()を使ったシグナルハンドラの登録方法を詳しく解説します。
なお、侍テラコヤという学習サイトでは、PythonやHTML/CSS/JavaScriptなどのプログラミング言語や機械学習、Webアプリなどを無料で学ぶことができますよ!
シグナルとは
まず、シグナルというのは簡単にいうと「プログラムの実行中に起きるイベント」のようなものだと思ってください。
例えば、プログラムの実行中にCtrl+Cを押すと、停止しますよね?
あのようなイベントがシグナルです。
そして、シグナルにはそれぞれ名前がついています。
先ほど紹介した、Ctrl+Cを押したときに送信されるシグナルはSIGINTです。
シグナルはこのように「SIG」+「シグナルの種類を表す文字列」で名前つけされています。
SIGINTのINTは、割り込みを意味するINTERRUPTのINTです。
その他にも、killコマンドを使うと送信されるSIGKILLやSIGTERMなどがあります。
C言語でシグナルを表す場合、それぞれのシグナルは整数値でdefineされています。
具体的には、signal.h内のsys/signal.hで定義されているのですが、一部を書き出してみました。
#define SIGHUP 1 /* hangup */
#define SIGINT 2 /* interrupt */
#define SIGQUIT 3 /* quit */
#define SIGILL 4 /* illegal instruction (not reset when caught) */
これを見ると、Ctrl+Cを押した時のシグナルSIGINTは整数で2を表していますね。
このシグナル一覧は、Linuxであれば以下コマンドでsingalコマンドのマニュアルを表示させても確認できますし、web上マニュアルを確認してもいいでしょう。
$ man signal
このdefine定数は、シグナルハンドラを登録する際に必要になります。
(かといって、数字まで覚える必要はありません。SIGINTなどのdefineされた定数名を使用します。)
シグナルの中でも、SIGALRMとSIGCHLDに関してはそれぞれ以下の記事でも解説しています。
シグナルハンドラとは
続いて、シグナルハンドラというのはプログラムがシグナルが送られた時に呼び出される関数のことです。
言い換えると、「シグナルが送られたときに、どういう処理をするのか」を決める関数のことを指します。
例えば、Ctrl+Cを押したら、「中断します」と出力して終わりたい時があるとしましょう。
そういう時に、シグナルハンドラが必要になります。
シグナルハンドラを使用しないと、そのまま処理が中断されてしまうからです。
シグナルハンドラの登録は、signal関数を使った方法と、sigaction関数を使った方法があるので、今回は両方をわかりやすく解説していきます!
signal関数の使い方
signal関数の使い方はすごく簡単です。
signal(“シグナルの種類”, “シグナルハンドラとしたい関数”)という形でシグナルが起きた時にシグナルハンドラが呼び出されます。
“シグナルの種類”は最初に説明したSIGINTやSIGKILLなどのdefine値が入ります。
例えば、signal(SIGINT, function)とすれば、SIGINT(Ctrl+Cを押した時に起きるシグナル)が起きると、functionが呼び出されるといった感じです。
functionが、「中断します」と出力する関数であれば、Ctrl+Cを押した時に「中断します」と出力してプログラムが終了します。
signalの実装例
Ctrl+Cを押したときに送信されるシグナルであるSIGINTをキャッチしたときに、signal_handlerという関数を呼び出す実装例です。
#include <stdio.h>
#include <signal.h> /* signal.hをインクルード */
#include <stdlib.h>
/* シグナルハンドラとしたい関数の定義 */
void signal_handler(int signum)
{
/* シグナルをキャッチしたときに実行したい内容 */
puts("中断します");
exit(1);
}
int main(void){
/* シグナルハンドラの登録 */
signal(SIGINT, signal_handler);
/* Ctl+Cで強制終了されるまで無限ループ */
while(1){
}
return 0;
}
シグナルハンドラに設定したい関数は、返り値なしでint型の引数を取る関数にする必要があります。
このint型の引数には、キャッチしたシグナルの値が入るのです。
sigactionの使い方
sigactionはsignalより複雑になります。
簡単にsigactionの使い方の流れを書きますので一つずつみていきましょう。
- struct sigaction型の構造体変数saを宣言する
- sigemptyset(&sa.sa_mask)でsaのシグナルマスクをクリアする
- sa.sa_handlerにシグナルハンドラにしたい関数を割り当てる
- sa.sa_flagsにフラグを設定する(とりあえず0でOKです)
ちなみに、最初の変数宣言以外は順番は関係ありません。
では一つずつみていきましょう。
struct sigaction型の構造体変数saを宣言する
実は、sigaction()を使う時に必要なstruct sigactionという構造体型があります。
その定義は、以下のようになっています。
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
これを見ただけではなんのこっちゃという感じだと思いますが、とりあえずこういう構造体があるんだなって思ってもらえるだけでOKです。
とりあえず、このstruct sigaction型の構造体変数saを宣言しておきましょう。
struct sigaction sa;
sigemptyset(&sa.sa_mask)でsaのシグナルマスクをクリアする
シグナルマスクというのは、「〇〇のシグナルハンドラの実行中は、××のシグナルは無視する」っていう時に便利な機能です。
シグナルというのはイベントが起きるとプログラムの実行部分に関係なく割り込んで来るので、それを防ぐことができます。
ただ、シグナルを複数取り扱うとかでない限り、とりあえずはあまり気にしないでいいでしょう。
sigemptyset()を使うと、シグナルマスクをゼロクリア(マスクの設定は特になし)にすることができます。
引数は、struct sigaction型の構造体変数のメンバsa_maskのアドレスを渡します。
sigemptyset(&sa.sa_mask)
sa.sa_handlerにシグナルハンドラにしたい関数を割り当てる
続いてやっとシグナルハンドラのセットです。
シグナルハンドラにしたい関数をfunction()とすると、struct sigaction型の構造体saのメンバsa_handlerにfunctionを割り当てます。
sa.sa_handler = function;
関数の代入なんてよく分からない!という人もいるかもしれませんが、とりあえずこうすることで関数を割り当てることができると思ってください笑
関数ポインタに関してはまた別記事で紹介したいと思います。
sa.sa_flagsにフラグを設定する
最後にsa.sa_flagsにフラグを代入します。
ただ、特にようもなければsa.sa_flags = 0で大丈夫です。
フラグというのは、例えばシグナルの詳細な情報が欲しい時などに、SA_SIGINFOを指定したりします(その場合、sigaction()の使い方が少し変わってくるので今回は割愛します)。
sigactionの実装例
最後にsigactionを使った例を2つ紹介します。
1つ目はあくまでsigactionの使用例を分かりやすくするためのコードです。
2つ目は1つ目のコードに安全性を考慮に入れて書き直したものになります。
実用的なのは2つ目ですが、sigactionの使い方を理解するという意味では1つ目の例を参考にしてもらえれば大丈夫です。
sigactionの実装例1
1つ目の例はCtrl+Cを押したら「中断します」と表示して終了するプログラムです。
これは実はあまりよくない例ですが、とりあえずsigaction()の使い方を知るという目的なのでご容赦ください。
具体的にどこがよくないと言いますと、シグナルハンドラの中でputs()というシグナル内で使うには安全ではない関数を使っていたり、sigaction()のエラーチェックをしてないなどですが、今はあまり気にしなくてOKです。
#include <stdio.h>
#include <signal.h> /* signal.hをインクルード */
#include <stdlib.h>
#include <string.h>
int count;
/* シグナルハンドラとしたい関数の定義 */
void signal_handler(int signum)
{
/* シグナルハンドラ内で安全ではない関数puts()を使っている */
puts("中断します");
exit(1);
}
[c][/c]
#include <stdio.h>
#include <signal.h> /* signal.hをインクルード */
#include <stdlib.h>
#include <string.h>
int count;
/* シグナルハンドラとしたい関数の定義 */
void signal_handler(int signum)
{
/* シグナルハンドラ内で安全ではない関数puts()を使っている */
puts("中断します");
exit(1);
}
int main(void){
/* シグナルハンドラの登録 */
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_handler = signal_handler;
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
/* Ctl+Cで強制終了されるまで無限ループ */
while(1){
}
return 0;
}
sigactionの実装例2
次に、シグナルハンドラ内で「安全ではない関数」を使わずに、sigactionのエラーチェックも入れた例を紹介します。
何をしているかというと、メインループでは1秒おきにカウンタ変数countをインクリメントしているのですが、Ctrl+Cを押すと、一気にカウンタが+100されて、ループの条件を抜けて終了するといった感じです。
まあ実用的とは言えないですが、本来のシグナルハンドラはこのように、カウンタを変更したり、フラグをセットしたりと簡単な処理だけをさせるように使います。
こんな感じで、Ctrl+Cを押してもすぐに終わらないプログラムを作ることが可能です。
#include <stdio.h>
#include <signal.h> /* signal.hをインクルード */
#include <stdlib.h>
#include <string.h>
int count;
void signal_handler(int signum)
{
/* シグナルハンドラ内で安全ではない関数を使っていない */
count += 100;
}
int main(void){
struct sigaction sa;
/* シグナルマスクのクリア(エラーチェック付き) */
if(-1 == sigemptyset(&sa.sa_mask){
exit(1);
}
sa.sa_handler = signal_handler;
sa.sa_flags = 0;
/* シグナルハンドラの登録(エラーチェック付き) */
if(-1 == sigaction(SIGINT, &sa, NULL)){
exit(1);
}
/* 変数countが50以下の間ループ */
while(count < 50){
}
puts("Count Over.");
return 0;
}
まとめ
この記事ではsignalとsigactionを使ったシグナルハンドラの設定方法を紹介しました。
signalではシグナルハンドラを簡単に設定できますが、細かい設定ができません。
sigactionの使い方は少しややこしいですが、signalよりも細かい設定が可能で、POSIXでも推奨されています。
ぜひ使い方を理解してシグナルハンドラを扱えるようになりましょう。
なお、侍テラコヤという学習サイトでは、PythonやHTML/CSS/JavaScriptなどのプログラミング言語や機械学習、Webアプリなどを無料で学ぶことができます!
メールアドレスだけで簡単に無料登録できるのでぜひ覗いてみてくださいね。
\無料プランを無期限で試す/
メールアドレスだけで10秒で登録!
コメント