シグナルの操作(2)
ツイート前回はsignal(2)の使い方とその問題点について解説しました。
ここでは、singal(2)の問題点を解決したPOSIXシグナルシステムコールについて解説します。
今からシグナルを使いたいという方はこちらを参照してください。
POSIXシグナルシステムコール
シグナルの問題点のおさらい
非常に重要ですので、これまでに前回出てきた問題点を整理しましょう。
- 問題点1:シグナル番号はシステムにより異なる、もしくは将来には変更される可能性があるので、数値での使用は避ける必要がある。
- 問題点2:シグナルセーフ問題。シグナルハンドラ(コールバック関数)内では使えるシステムコールは限られている。
- 問題点3:リエントラント問題。signal(2)はリエントラント時の動作が実装によって異なる。
- 問題点4:移植性。実装により動作が異なるので、異なるOSへの移植は非常に難しい。
- 問題点5:スレッド問題。解説はしていないが(別途行う予定)、様々な問題がある。
sigaction(2)
sigaction(2)のプロトタイプ
これらの問題によりsignal(2)は使うべきではないという結論になりました。この問題に対してUnix設計者(POSIX)はsigaction(2)を作成しました。
sigaction(2)はシグナルマスクという考え方を導入し、プログラマが動作を設定出来るようにしました。
これによりSysVやBSDと同じ動作にする事ができ、OS間での移植が可能になっています。
ではプロトタイプを見てみましょう。
#include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
ヘッダは同じですね。actは変更するシグナルマスク、oldactは変更前のシグナルマスクです。
actがNULLであれば動作は変更されません。oldactがNULLであれば変更前のシグナルマスクは取得しません。
シグナルマスクはこの様に定義されています。しかしシステムによっては共用体が使われている場合もありますので、定義に依存した処理を書いてはいけません。
struct sigaction { void (*sa_handler)(int); //シグナルハンドラ void (*sa_sigaction)(int, siginfo_t *, void *); //細かなパラメータの指定など sigset_t sa_mask; //禁止すべきシグナルマスクの指定 int sa_flags; //シグナルハンドラの動作を変更するためのフラグの指定 void (*sa_restorer)(void); //廃止予定であり使用すべきではない };
sigaction(2):SysV風動作
sigaction(2)はsignal(2)に比べて汎用性がある分使い方が多少難しくなります。
では、まず馴染みの深いSysVと同じ動作になるシグナル操作を見ていきます。
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <string.h> void handler(int signo) { char buf[] = "Signal:Catch"; write(1, buf, sizeof(buf)); } int main(void) { int ret; struct sigaction act; //sigactionの設定 sigset_t sigset; //sa_maskに設定するシグナルマスク //sigsetを初期化 ret = sigemptyset(&sigset); if (ret < 0) { return 1; } //シグナル・ハンドラ実行中に禁止するシグナル ret = sigaddset(&sigset, SIGINT); if (ret < 0) { return 1; } memset(&act, 0, sizeof(act)); act.sa_handler = handler; //シグナルハンドラを設定 act.sa_mask = sigset; //シグナルマスクを設定 //SysV風シグナル動作 act.sa_flags |= SA_NODEFER; //SA_NOMASKは古いので使わないように!! act.sa_flags |= SA_ONSTACK; //actの条件でSIGINTの設定をする ret = sigaction(SIGINT, &act, NULL); if (ret < 0) { return 1; } //シグナルを待つ pause(); return 0; };
シグナルマスクを扱うためには構造体を直接触らずに、sigemptyset(2),sigfillset(2),sigaddset(2),sigdelset(2),sigismember(2)を使います。
これらの設定をした後に、sa_maskに設定を行います。
sigaction(2):BSD風動作
続いてBSD風操作です。Linuxに慣れている人はあまり馴染みがないのですが移植性も考えると知っておく必要があるでしょう。
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <string.h> void handler(int signo) { char buf[] = "Signal:Catch"; write(1, buf, sizeof(buf)); } int main(void) { int ret; struct sigaction act; //sigactionの設定 sigset_t sigset; //sa_maskに設定するシグナルマスク //sigsetを初期化 ret = sigemptyset(&sigset); if (ret < 0) { return 1; } //シグナル・ハンドラ実行中に禁止するシグナル ret = sigaddset(&sigset, SIGINT); if (ret < 0) { return 1; } memset(&act, 0, sizeof(act)); act.sa_handler = handler; //シグナルハンドラを設定 act.sa_mask = sigset; //シグナルマスクを設定 //BSD風シグナル動作 act.sa_flags |= SA_RESTART; //actの条件でSIGINTの設定をする ret = sigaction(SIGINT, &act, NULL); if (ret < 0) { return 1; } //シグナルを待つ pause(); return 0; };
基本的に同じで、sa_flagsの設定が違うだけです。
個人的にはこちらのほうが素直だとは思います。
sigprocmask(2)
さらに、特定の範囲だけシグナルからの割り込みを禁止する方法も提供されています。
すでに書いた通り、シグナルハンドラは非常に短い処理のみを書くことが推奨されますが、ある程度の処理を書きたい場合にはジレンマが発生します。
この場合、シグナルハンドラ側でフラグを設定しておき、シグナルハンドラから戻ってから処理を行うというテクニックがあります。
int flg = 0; void handler(int signo) { flg = 1; } void any_proc(void){ //何かの処理 if (flg == 1) { //例えば一度に処理したいハードウェア操作とか } } void func(void){ sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGINT); //SIGINT割り込みを禁止 sigprocmask(SIG_BLOCK, sigset, NULL); any_proc(); //SIGINT割り込みを許可 sigprocmask(SIG_UNBLOCK, sigset, NULL); }
これはシグナルハンドラ側での操作が悪い影響を与える場合などに有効です。
まとめ
POSIXシグナルはシグナルの動作を移植性が高く、汎用的に実現する手段です。
Unixシステムプログラマの方は、これを機会にsignal(2)の使用はやめてsigaction(2)に乗り換えませんか?
ピアソンエデュケーション
売り上げランキング: 163169
ソフトバンククリエイティブ
売り上げランキング: 451196