Linux シグナル入門

2013-11-5 (鈴)

1. シグナルとは?

プロセスが止まらないとき,キーボードから Control-C を打鍵して止めることがよくある。 何もプログラムに Control-C の入力を処理するコードを書いたわけではないのに止まる。 なぜ止まるのだろう。

01:~/tmp$ cat forever.c
int main()
{
    for (;;) {
    }
    return 0;
}
01:~/tmp$ gcc forever.c
01:~/tmp$ ./a.out
^C
1301:~/tmp$  

それはシグナル (signal) のおかげである。

ここでは特定の用語として使っていますが,一般に signal (シグナル) とは信号や合図を意味する英語の普通名詞です。 例えば「青信号」は green signal です。signal processing は「信号処理」です。 語源をさかのぼると sign (サイン) と同じくフランス語を経てラテン語の中性名詞 signum (シグヌム) にたどり着きます。 これはしるし,目印,合い言葉などを意味しました。

計算機の CPU はさまざまなハードウェアからの割り込み要求 (interrupt request) を非同期的に受け付ける。 シグナルとは,このような CPU へのハードウェア的な割り込み要求に似た仕組みを,オペレーティング・システムがソフトウェア的に実現したものである。 ハードウェアの割り込み要求が CPU へと送られるのと同じように,シグナルはプロセスへと送られる。

つまり,シグナルとは,プロセスに送られるソフトウェア的な割り込み要求である。

言うまでもないことですが (シグナルを)「送る」,(プロセスに)「割り込む」はあくまで計算機内部のソフトウェアの動作の直感的な比喩です。 動作の実体としては,オペレーティグ・システムのカーネルが利用者プロセスの現在の実行をいったん止めて, そのシグナルに設定された処理をプロセスに実行させます。 以下,慣習にしたがった比喩を使って説明します。

我々が学んでいるオペレーティング・システムである GNU/Linux (以下単に Linux) は,他の Unix 類と同じくキーボードから Control-C の打鍵を受け取ると SIGNIT と呼ばれるシグナルをプロセスに送る。

   SIGINT (SIGnal INTerrupt, シグイント)
SIGINT は #include <signal.h> による記号名として C 言語から使うことができます。 この記号名は端末からの割り込み (terminal interrupt) に由来します。

普通,オペレーティング・システムがキーボードへの打鍵を受け付ける機構としてハードウェアの割り込み要求が使われている。 しかし,それはオペレーティング・システムのカーネルの中で消化され,プロセスには届かない。 プロセスに送られる SIGINT とは,あくまでそれを契機としてカーネル内で作られたソフトウェアの割り込み要求である。

ところで「シグナルを送る」というとき,実際には何が送られているのだろうか?

シグナルとは実際には小さな正の整数である。 各シグナルを表現するためにそれぞれ決まった整数が使われる。 SIGINT は伝統的に整数 2 で表される。

#define SIGINT  2       /* interrupt */
2 という値自体に特に必然性はありません。 歴史の「もしも」で別の小さな整数になったとしても Unix 類は成立します。 後述する理由から 0x80 == 128 より小さな正の整数でなければならない制約がありますが,その範囲で任意に決定できます。 ただし,実際には Unix の草創期から定義されている 1 から 15 までのシグナルは伝統を継承し,どの Unix 類でも (基本的には) 共通の値に定義しています。 記号名でいえば 1 から順に SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT(=SIGIOT), [SIGEMT], SIGFPE, SIGKILL, [SIGBUS], SIGSEGV, [SIGSYS], SIGPIPE, SIGALRM, SIGTERM の 15 個です。 ただし [] で囲ったシグナルは Linux ではアーキテクチャ依存です。

つまり,Control-C が打鍵されると整数 2 がシグナルとしてプロセスに送られる。

古典的な Unix ではシグナルとは単に整数でした。 その基本は変わらず,多くのプログラムは従来から,そしてこれからもおそらくシグナルすなわち整数として扱います。 ここでの説明でもその範囲でだけ説明します。 しかし,内部的には整数だけではありません。 Linux を含む現在のほとんどの Unix 類では,シグナルに付随する情報を構造体へのポインタとして利用者プログラムから取得できます。 ここでは扱いませんが興味のある人は sigaction(2) の siginfo_t 構造体を調べてみてください。

プロセスは1機械語命令ごとの細かな粒度でいつでもシグナルとして整数を受け取る。

つまり,カーネルはプロセスの実行を1機械語命令ごとの単位でいつでも止めて,シグナルの処理を強いることができます。

SIGINT つまり整数 2 のシグナルを受け取ったとき,プロセスが特に何も処理方法を設定していなかったならば,Linux はそのプロセスをただちに終了させる。 これが Control-C の打鍵で前述の forever.c プログラムが止まった理由である。

つまり,シグナルとは,ハードウェアの割り込み要求そのものではないが,まるでそうであるかのように利用者プロセスの動作に (図々しく) 割り込む,ある規定された整数定数の通知である。

ところで最初の例で ^C つまり Control-C の打鍵でプロセスを終了させたとき,プロンプトに現れる数字が "1301" となっています。これは何を意味するのでしょうか?

この例では bash のシェル変数 PS1\[\e[34m\]$?$SHLVL:\w\[\e[31m\]\$\[\e[0m\] と設定しています。 通常のときプロンプトに現れている数字の "01" は $?$SHLVL がそれぞれ 0 と 1 であることを表しています。 $? は直前に実行したコマンドの終了ステータスです。 $SHLVL (シェルレベル) はシェルをどれだけ入れ子で起動したかです。 つまり,終了ステータスが 0 の正常終了 (でシェルが入れ子の底のレベル 1) だから "01" というわけです。

これと同じように,シグナルで停止した ./a.out の終了ステータスが 130 だったことが "1301" の理由です。 シェルはプロセスがシグナルで強制終了したとき,シグナルの整数値に 8 ビット目のビットを立てて終了ステータスとします。 終了ステータス 130 とは整数値 2 の SIGINT で強制終了したことを意味します。
   (2 | 0x80) == 130
Windows 上で Linux と似た環境を実現する Cygwin でも終了ステータスの構成方法は同じです。 Life with Cygwin 26-4 では整数値 10 の SIGBUS (シグバス,bus error の発生を表す) により終了ステータス 138 (= 10 | 0x80) で終わる例を示しました。

ここで「オペレーティング・システムは」ではなく「シェルは…終了ステータスとします」という微妙な言い回しをした理由については第5章の「終了ステータスについての補足」で説明します。


2. シグナル・ハンドラと signal システム・コール へ続く


Copyright (c) 2013 OKI Software Co., Ltd.