はじめに
シグナルはUNIXなどのOSにおける非同期イベントを通知する仕組みですが、「最古のプロセス間通信」「SysV系とBSD系で動きが違う」「昔のシグナルを使うと汎用性が無くなってしまう」「スレッドと相性悪し」といった理由からか、私の経験上、業務系アプリケーションではあまり使われていません。たまに使っているものを見ると、誤った使い方をしているものが多かったりします。
ところがこのシグナル、いろいろ調べていくと意外と奥が深く、有用に使えるのに、以外と情報が少ないことがわかりました。
今さらな感もありますが、ここでは私が知っている事や、調べた結果、今までの経験から得た知識などをまとめてみたいと思います。
ただし、環境によって動作が違うことが想定され、また一部のプログラムは環境に対し重大な影響を与えてしまうものもあります。プログラム等の使用に際しては十分に注意して頂き、ご自身の責任の範囲で行ってください。
サンプルはすべてCです。環境はFedora Core 6(2.6.18-1.2798.fc6)、32bit、glibc-2.5-3、gcc-4.1.1-30で動作確認しています。
1.シグナルの概要
UNIXやPOSIX準拠のOSにある「シグナル」は、一種の並行プログラミングの機能で最古にして簡潔なプロセス間通信です。
そもそもシグナルのアイデアはハードウェア・プログラミングから来ています。例えば「ハードウェア割り込み」という出来事は、あらかじめ予定された通りに起きるものではなく、想定外のタイミング、つまり「非同期」で発生します。
普通、このようなハードウェアの操作はOSの仕事であり、アプリケーションレベルでは必要な機能ではありませんでした。しかし割り込みや例外の概念はとても便利で、ユーザープロセスとして動作するプログラムにも何とか利用できないかと実装が考慮されました。結果、OSには「ソフトウェア割り込み」としての「シグナル」機能が実装されたのです。
OSにとってシグナルは「非同期なイベントを処理するイベントドリブン(イベント駆動型)プログラミング」を実現するための機能です。下記は一般的に重要と思われるシグナルイベントです。
- A:プロセスの終了、再定義可能。
- B:シグナルを無視、再定義可能。
- C:プロセスの終了とコアダンプ、再定義可能。
- D:プロセスの一旦停止、再定義可能。
- E:プロセスの終了、再定義不可能。
シグナルイベント | 種類 | 説明 |
SIGINT | A | キーボードからの割り込み、Ctrl+Cが押された。 |
SIGALRM | A | アラームタイマーの時間満了、もしくはalarm(2)からのタイマーシグナル。 |
SIGHUP | A | 制御端末のハングアップ、デーモンに対しては再起動要求。 |
SIGCHLD | B | そのプロセスが起動した子プロセスが終了した。 |
SIGFPE | C | 浮動小数点演算のエラーおよび整数の0による割算の検出。 |
SIGSEGV | C | メモリアクセスについて違反があった。 |
SIGTSTP | D | プロセスの一旦停止、Ctrl+Zが押された。 |
SIGCONT | - | 「バックグラウンドに回った」プロセスを再開する。シェルで「fg」と叩かれた等。 |
SIGTERM | A | プログラムは速やかに終了しなくてはいけない。子プロセスがある場合、そのプロセスが子プロセスも終了させることを想定している。killallを実行するとこのシグナルが発生している。 |
SIGQUIT | C | キーボードからの割り込み、Ctrl+\(Winの場合はCtrl+Break)が押された。 |
SIGSTOP | E | 実行を一時停止。SIGCONTシグナルによって再開。 |
SIGKILL | E | プロセスの強制終了。 |
上記表以外にもシグナルはたくさんあります。上記の実際の値は「asm/signal.h」に定義されていますし、man 7 signal
でたくさん出てきます。このようなシグナルは上に述べたものを含めてすべてシェルコマンドの「kill」によって生成できます。
$ ./a.out & [1] 12345 $ kill 12345 [1]- Terminated ./a.out
この場合、killコマンドはデフォルトのシグナルであるSIGTERMを、プロセス番号12345である「./a.out」に送ります。「./a.out」はSIGTERMを受け取るとデフォルト動作として「終了」します。killコマンドは以下のようにして、任意のイベントをシグナルとして送ることができます。
$ kill -KILL `pgrep a.out`
-KILL
はSIGKILLの「SIG」を取った名前です。上記ではSIGKILL が「a.out」のプロセス番号12345に送られます。
子プロセスを起動しているプログラムの場合、親プログラムのプロセス番号を負にしてkillを起動すると、それが「プロセスグループ」の番号であると解釈され、子プロセス含めてシグナルが送られます。