システムインタフェース

第 5 章 シグナル

この章では、シグナルがアプリケーションに対してできることと、シグナルをどのようにして使用できるようにするかについて説明します。

概要

シグナルは、イベントが発生したときにプロセスに送られるソフトウェア生成割り込みです。シグナルは、SIGFPE や SIGSEGV など、アプリケーション内のエラーによって同期して生成されることもありますが、ほとんどは非同期です。システムで、別のプロセスからユーザが「割り込み」、「停止」、または「終了」要求を入れるなどのソフトウェアイベントを検出すると、シグナルをプロセスに送信できます。バスエラーや不当な命令などのハードウェアイベントが検出されると、カーネルから直接シグナルを送ることもあります。

プロセスに配信できる一連のシグナルは、システムで定義されています。シグナルの配信は、ハードウェア割り込みの発生に似ています。つまり、シグナルをそれ以降に発生したシグナルからブロックされ配信されないようにできます。配信先プロセスがシグナルに対応して動作を起こさないと、ほとんどのシグナルはそのプロセスを終了します。配信先プロセスを停止させるシグナルや、無視してもよいシグナルもあります。各シグナルのデフォルトの動作は、次のいずれか 1 つです。

システムによって定義されるシグナルは、次の 5 種類に分類できます。

シグナルのセットは、<signal.h> ヘッダに定義されています。

シグナル処理

シグナルはプロセスに配信されると、そのプロセスに対して保留状態にある一連のシグナルに加えられます。シグナルは、プロセスに対してブロッキングされていなければ配信されます。シグナルが配信されるとプロセスの現在の状態が保存され、新しいシグナルマスクが設定され、シグナルハンドラが呼び出されます。

BSD シグナルセマンティクスでは、シグナルがプロセスに配信されると、プロセスのシグナルハンドラの持続期間 (またはマスクを修正するインタフェースが呼び出されるまで)、新しいシグナルマスクがインストールされます。シグナルハンドラが実行している間は、このマスクは配信されたばかりのシグナルが、またプロセスに割り込まないようにブロッキングします。

System V シグナルセマンティクスでは、この保護を提供していないので、同じシグナルがそのシグナルを処理しているハンドラに割り込むことができます。このため、シグナルハンドラは再入可能であることが必要です。

すべてのシグナルは、同じ優先順位です。シグナルハンドラが呼び出したシグナルをブロッキングしても、他のシグナルをプロセスに配信できます。

シグナルはスタックされません。シグナルハンドラが、1 つのシグナルが実際に配信された回数を記録する方法はありません。

ブロッキング

グローバルシグナル「マスク」は、プロセスへの配信をブロッキングされる一連のシグナルを定義します。プロセスのシグナルマスクは、その初期状態を親プロセスのシグナルマスクからコピーします。sigaction(2)sigblock(3B)sigsetmask(3B)sigprocmask(2)sigsetops(3C)sighold(3C)、または sigrelse(3C) を呼び出すと、マスクを変更できます (詳細は、signal(3C) を参照してください)。

ハンドリング

アプリケーションプログラムは、特定のシグナルが受信されると呼び出される関数「シグナルハンドラ」を指定できます。シグナルを受信してシグナルハンドラが呼び出されることを、シグナルを「キャッチする」と言います。プロセスは、次のいずれか 1 つの方法でシグナルを処理できます。

シグナルハンドラは、通常はプロセスの現スタック上で実行します。こうするとシグナルハンドラは、プロセスで実行が割り込まれた位置に戻ることができます。シグナルハンドラが特定のスタックに実行するように、これをシグナルごとに変更できます。割り込まれたコンテキスト以外のコンテキストで再開しなければならない場合は、プロセスは以前のコンテキスト自体を復元しなければなりません。

シグナルハンドラのインストール

signal(3C)sigset(3C)signal(3B)、および sigvec(3B) すべてを使用して、シグナルハンドラをインストールできます。これらのすべては、シグナルに対する前の動作を戻します。4 つのインタフェースには、1 つの重要な違いがあります。signal(3C) は System V シグナルセマンティクスとなります (同じシグナルがそのハンドラに割り込むことができます)。 sigset(3C)signal(3B)、および sigvec(3B) は、すべて BSD シグナルセマンティクスになります (シグナルはそのハンドラが復帰するまでブロッキングされます)。さらに、signal(3C)signal(3B) の両方とも、シグナルを無視するかデフォルトの動作を復元するかを制御できます。例 5-1 は、簡単なハンドラとそのインストールを示しています。


例 5-1 シグナルハンドラのインストール

#include <stdio.h>
#include <signal.h>

void sigcatcher()
{
		printf ("PID %d caught signal.¥n". getpid());
}

main()
{
		pid_t ppid;

		signal (SIGINT, sigcatcher);
		if (fork() == 0) {
			sleep( 5 );
			ppid = getppid();
			while( TRUE )
				if (kill( ppid, SIGINT) == -1 )
					exit( 1 );
		}
		pause();
}

シグナル SIGKILL または SIGSTOP に対して、ハンドラのインストールまたは SIG_IGN の設定を試みるとエラーになります。シグナル SIGCONT に対して SIG_IGN の設定を試みてもエラーになります。これはデフォルトで無視されているためです。

シグナルハンドラをインストールすると、signal(3C)sigset(3C)signal(3B)、または sigvec(3B) をもう一度呼び出して明示的に置き換えるまで、インストールされたままになります。

SIGCHILD のキャッチ

子プロセスが停止または終了すると、SIGCHILD が親プロセスに送られます。このシグナルへのデフォルトの応答は無視することです。このシグナルをキャッチでき、すぐに wait(2) および wait3(3C) を呼び出して、子プロセスからの終了ステータスを得ることができます。こうすると、ゾンビプロセスのエントリをできるだけ素早く削除できます。例 5-2 は、SIGCHILD をキャッチするハンドラのインストールを示しています。


例 5-2 SIGCHILD をキャッチするハンドラのインストール

#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/resource.h>

void proc_exit()
{
		int wstat;
		union wait wstat;
		pid_t	pid;

		while (TRUE) {
			pid = wait3 (&wstat, WNOHANG, (struct rusage *)NULL );
			if (pid == 0)
				return;
			else if (pid == -1)
				return;
			else
				printf ("Return code: %d¥n", wstat.w_retcode);
		}
}
main ()
{
		signal (SIGCHLD, proc_exit);
		switch (fork()) {
			case -1:
				perror ("main: fork");
				exit (0);
			case 0:
				printf ("I'm alive (terporarily)¥n");
				exit (rand());
			default:
				pause();
		}
}

SIGCHILD をキャッチャするハンドラは、通常はプロセス初期化の一部として設定します。これらは、子プロセスをフォークする前に設定しなければなりません。典型的な SIGCHILD ハンドラは、子プロセスの終了状態を検索するだけです。