この記事は、Linux シグナルの基本と仕組み (カーネルでの実装) について調査したことのまとめです。
シグナルは普段から利用しているものの仕組みについては理解してなかったので、カーネル勉強の題材として調べてみました。想像以上に複雑でボリュームがあったため、書き切れていない部分 (調査しきれなかった部分) もありますが、一通りの流れ (仕組み) は理解できたと思います。
なお、この記事は主に「■ 基本編」と「■ カーネル編 (v5.5)」で構成されています。仕組みを理解するには基本も知る必要があると思い、このような構成となっています。書籍レベルの基本を理解されている方は 「■ カーネル編 (v5.5)」 から読み進めることを推奨します。
■ 基本編
はじめにシグナルの基本について、ざっと整理します。
なお、例で登場するコマンドや API (C 言語) の細かい使い方やエラー処理などは省きます。詳細は man や参考文献の情報等をご活用ください。
1. シグナルとは
プロセスやプロセスグループへ様々なイベントを通知するためにあるカーネルの機能です (ソフトウェア割り込み)。イベントの通知は様々な場所 (自分/他プロセス、カーネル) から行うことが可能で、次のようなことができます。
- ハングしたプロセスにシグナルを送信して強制終了させる
- シグナルを送信してプロセスの処理を一時停止・再開させる
- ハードウェア例外 (0 除算、メモリアクセス違反など) 時にシグナルを送信してプロセスを終了させる
- シグナルを送信する特殊なキー (Ctrl + C など) を入力しプロセスを終了させる
- シグナル受信時にユーザ定義の関数 (シグナルハンドラ) を実行させる
なお、シグナルは次のような流れで処理されます。詳細は追々見ていきます。
2. シグナル利用例
イメージし易いように、ユーザ視点でのシグナル利用例を 3 つ記載します。
kill コマンド
kill コマンドでシグナル (SIGTERM) を送り sleep プロセスを終了させます。
$ ps
PID TTY TIME CMD
2662 pts/0 00:00:00 bash
2679 pts/0 00:00:00 sleep
2685 pts/0 00:00:00 ps
$ kill 2679
[1]+ Terminated sleep 1000
$ ps
PID TTY TIME CMD
2662 pts/0 00:00:00 bash
2696 pts/0 00:00:00 ps
割り込みキー
割り込みキー (Ctrl + C) でシグナル (SIGINT) を送り、無限ループするコマンド (プロセスグループ) を終了させます。
$ while :; do sleep 1; echo "Stop me !"; done
Stop me !
Stop me !
Stop me !
^C
プログラム (C 言語 API)
以下のように自前のプログラム (send_sig.c) でシグナル (SIGTERM) を送り sleep プロセスを終了させることもできます。
#include <signal.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
pid_t pid = atoi(argv[1]);
kill(pid, SIGTERM);
return 0;
}
コンパイル & 実行例
$ ps
PID TTY TIME CMD
30285 pts/0 00:00:00 bash
30597 pts/0 00:00:00 sleep
30631 pts/0 00:00:00 ps
$ gcc -o send_sig send_sig.c
$ ./send_sig 30597
[1]+ Terminated sleep 1000
$ ps
PID TTY TIME CMD
30285 pts/0 00:00:00 bash
30663 pts/0 00:00:00 ps
3. シグナル番号とシグナル名
シグナルは、用途に応じて番号と名前が割り振られています。
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
ただし、シグナル番号の一部はアーキテクチャに依存するため、上述の実行例 (x86_64) とは番号が異なる場合があります 1。
4. 標準シグナル、リアルタイムシグナル
シグナルは、標準シグナルとリアルタイムシグナルの 2 種類に大別できます。
ざっくりとした違いは、
- 標準シグナル ... SIGKILL など伝統的に使用しているシグナル (1 〜 31 番)
- リアルタイムシグナル ... 標準シグナルの拡張版 (32 〜 64 番)
です。もう少し細かい違いは、次のとおりです。
標準シグナル | リアルタイムシグナル | |
---|---|---|
新/旧 | 旧 | 新 |
シグナル名 | シグナル毎に様々 | SIGRTMIN(+n), SIGRTMAX(-n) |
シグナル番号 | 1 〜 31 | 32 〜 64 |
標準動作 (詳細は後述) | シグナル毎に様々 | プロセス終了 (全シグナル) |
用途 | シグナル毎に様々 | ユーザが定義する (全シグナル) |
複数の同じシグナル 受信時の挙動 |
1 つのみ受信 | 全て受信 |
複数の同じシグナル 送信時の順序 |
規定なし | 送信された順に到着 |
複数の異なるシグナル 送信時の順序 |
規定なし | シグナル番号の小さい順に到着 |
なお、本稿ではこれ以上深くリアルタイムシグナルについては言及しません 1。
5. シグナルアクション
シグナル受信時の動作 (シグナルアクション) には、次の 3 種類があります。この動作は後述の sigaction() などから変更できます。ただし、SIGKILL と SIGSTOP は標準動作以外にはできません。
-
無視
シグナルを受信しても何もしません。
(設定するには、後述の sigaction() などで sa_handler に SIG_IGN を指定する)
-
標準動作
シグナル受信すると、シグナル毎に定義された標準動作 (後述の Term, Ign, Core, Stop, Cont) を実行します。
(設定するには、後述の sigaction() などで sa_handler に SIG_DFL を指定 (デフォルト) する)
-
シグナルハンドラ
シグナル受信すると、ユーザが定義した動作を実行します。
(設定するには、後述の sigaction() などで sa_handler へユーザ定義の関数を指定する)
標準動作
標準動作は、シグナル毎に定義されており、次の 5 種類があります。
動作名 | 意味 |
---|---|
Term | プロセス終了 |
Ign | 何もしない (sigaction() で設定できる SIG_IGN と同じ) |
Core | プロセス終了とコアダンプ生成 2 |
Stop | プロセス一時停止 (TASK_STOPPED 状態に遷移させる) |
Cont | 一時停止したプロセスの再開 (TASK_STOPPED 状態からの復帰) |
ただし、これは標準シグナルに限ります。リアルタイムシグナルの場合は Term のみです (次図参照)。
標準シグナルと対応する標準動作
1 から 31 番までの標準シグナルは、各々に紐づく標準動作を持ちます。
シグナル名 | シグナル番号 (x86_64) | 標準動作 | 意味 |
---|---|---|---|
SIGHUP | 1 | Term | 制御端末のハングアップ検出 or 制御プロセスの死 |
SIGINT | 2 | Term | キーボードからの割り込み (Ctrl + C) |
SIGQUIT | 3 | Core | キーボードからの終了 (Ctrl + Q) |
SIGILL | 4 | Core | 不正な命令 |
SIGTRAP | 5 | Core | デバッグ用のトレース/ブレークポイントシグナル |
SIGABRT | 6 | Core | abort(3) からの中断シグナル |
SIGBUS | 7 | Core | バスエラー (不正なメモリアクセス) |
SIGFPE | 8 | Core | 浮動小数点例外 |
SIGKILL | 9 | Term | Kill シグナル |
SIGUSR1 | 10 | Term | ユーザ定義のシグナル (その1) |
SIGSEGV | 11 | Core | 不正なメモリ参照 |
SIGUSR2 | 12 | Term | ユーザ定義のシグナル (その2) |
SIGPIPE | 13 | Term | パイプ破壊 (読み手の無いパイプへの書き出し) 3。 |
SIGALRM | 14 | Term | alarm(2) からのタイマーシグナル |
SIGTERM | 15 | Term | 終了シグナル |
SIGSTKFLT | 16 | Term | 数値演算プロセッサ (コプロセッサ)におけるスタックフォルト (未使用) |
SIGCHLD | 17 | Ign | 子プロセスの一時停止 (再開) または終了 |
SIGCONT | 18 | Cont | 一時停止プロセスの再開 |
SIGSTOP | 19 | Stop | プロセスの一時停止 |
SIGTSTP | 20 | Stop | 制御端末からの停止 (Ctrl + Z) |
SIGTTIN | 21 | Stop | バッググラウンドプロセスが制御端末を読み取った |
SIGTTOU | 22 | Stop | バッググラウンドプロセスが制御端末へ書き込んだ |
SIGURG | 23 | Ign | ソケットに帯域外データ、緊急データが存在する |
SIGXCPU | 24 | Core | CPU 時間の上限 (RLIMIT_CPU) を超えた 4。 |
SIGXFSZ | 25 | Core | ファイルサイズの上限 (RLIMIT_FSIZE) を超えた 4。 |
SIGVTALRM | 26 | Term | 仮想タイマ (プロセスのユーザモードでの CPU 時間) がタイムアウトした |
SIGPROF | 27 | Term | プロファイリングタイマがタイムアウトした |
SIGWINCH | 28 | Ign | 制御端末のウィンドウサイズが変更された |
SIGIO | 29 | Term | 非同期 I/O イベント |
SIGPWR | 30 | Term | 電源の異常 |
SIGSYS | 31 | Core | 不正なシステムコールを実行した 5。 |
シグナルアクションの変更例
SIGKILL と SIGSTOP 以外のシグナルアクションを変更するには、trap コマンド や sigaction() などの API (C 言語) を使用します。
trap コマンド
trap は Bash に組み込まれたビルトインコマンドの一種で、シグナルアクションを設定できます 6。
ここでは、SIGINT 受信時に何もしない (シグナル無視) ように設定 (第一引数 '') してみます。
$ trap '' SIGINT
この状態では、SIGINT を受信しても反応しないため、次の実行例のように割り込みキー (Ctrl + C) が効きません。
$ while true; do sleep 1; echo "Stop me !"; done
Stop me !
Stop me !
^CStop me ! <--- Ctrl + C 押下しても効かず、コマンド継続
Stop me !
Stop me !
^Z <--- Ctrl + Z 押下で停止 (SIGTSTP 送信)
[1]+ Stopped sleep 1
sigaction (C 言語 API)
sigaction() は、シグナルの動作を設定できる API (C 言語) です 7。
次のサンプルプログラム (set_sigh.c) では、SIGINT 受信時に handler 関数を実行するように設定してみます。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
static void handler(int signo)
{
/*
* 本来ハンドラ内では、非同期シグナルセーフな関数を使用するべきですが、
* ここでは、そうでない printf()、exit() などの関数を使用しています。
* 非同期シグナルセーフについては $ man 7 signal をご参照ください。
*/
printf(" ... Caught the SIGINT (%d)\n", signo);
exit(EXIT_SUCCESS);
}
int main(void)
{
unsigned int sec = 1;
struct sigaction act;
// SIGINT 受信時に handler() を実行するように設定。
memset(&act, 0, sizeof act);
act.sa_handler = handler;
sigaction(SIGINT, &act, NULL);
// Ctrl + C などで終了されるまで、1 秒ごとにメッセージを出力する。
for (;;) {
sleep(sec);
printf("Stop me !\n");
}
return 0;
}
次の実行例では、SIGINT 受信後 handler() によってメッセージ (... Caught the SIGINT (2)) を出力しプログラムを終了しています。
$ gcc -o set_sigh set_sigh.c
$ ./set_sigh
Stop me !
Stop me !
Stop me !
^C ... Caught the SIGINT (2) <--- Ctrl + C 押下で handler 関数が実行される
なお、その他の API として signal() がありますが、こちらは移植性の観点から非推奨となっています (古い API)。特別な理由が無い限りは使用を避けた方が良いと思います 8。
6. シグナルブロック
SIGKILL と SIGSTOP 以外のシグナルは、プロセス毎にブロックできます。
たとえば、あるプロセスが SIGINT を受信した場合、通常は標準動作によりプロセスが終了させられますが、SIGINT をブロックしていた場合は、SIGINT を受信しても、シグナル無視のように無反応となります。
シグナル無視との違いは、受信したシグナルを保留するか否かです。シグナル無視の場合はシグナルを保留しませんが、シグナルブロックの場合は保留できます。そのため、ブロックが解除されると、再度シグナルを受信する必要なく処理できます 9。
シグナルブロックの設定例
シグナルブロックを設定するには sigprocmask() などの API (C 言語) を使用します 10。
次のサンプルプログラム (block_sig.c) では、SIGINT をブロックしてから 5 秒後に SIGINT のブロックを解除してみます。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main(void)
{
unsigned int sec = 5;
sigset_t set;
// シグナル集合を空にし、SIGINT 追加
sigemptyset(&set);
sigaddset(&set, SIGINT);
// SIGINT をブロック (保留) する
sigprocmask(SIG_BLOCK, &set, NULL);
printf("Block the SIGINT for %d sec\n", sec);
sleep(sec);
// SIGINT のブロックを解除する
printf("\nPassed %d sec, unblock the SIGINT\n", sec);
sigprocmask(SIG_UNBLOCK, &set, NULL);
printf("Done !\n");
return 0;
}
実行例では SIGINT ブロック後に SIGINT を送信していますが、SIGNINT 受信時の動作が実行されるのはブロック解除後となっているのが分かります。
$ gcc -o block_sig block_sig.c
$ ./block_sig
Block the SIGINT for 5 sec
^C^C^C^C^C^C^C^C^C^C <--- Ctrl + C 押下しても反応しない
Passed 5 sec, unblock the SIGINT <--- SIGINT により終了するため、その後の "Done !" が無出力
7. シグナルの状態確認
シグナルブロック (保留)、無視といった状態は /proc/<PID>/status
から確認できます。
$ cat /proc/29498/status
Name: bash
--- snip ---
SigQ: 0/29305 // このプロセスの実 UID 宛にキューイングされたシグナル / キューイングされたシグナルの上限値
SigPnd: 0000000000000000 // 特定プロセス (スレッド) 宛の処理待ちシグナルの数
ShdPnd: 0000000000000000 // スレッドグループ全体宛の処理待ちシグナルの数
SigBlk: 0000000000010000 // ブロックされるシグナル (ビットマスク値)
SigIgn: 0000000000380004 // 無視されるシグナル (ビットマスク値)
SigCgt: 000000004b817efb // 捕捉待ちのシグナル (ビットマスク値)
--- snip ---
この中で、SigBlk、SigIgn、SigCgt は複数のシグナルをまとめて表現するために シグナルセット というマスク値で管理されているため、読み方が少しややこしいです。たとえば、SigIgn (0000000000380004) の場合は次のような意味になります (16進数を2進数に変換、1 の位置から対応するシグナルを判断)。つまり、PID 29498 の SigIgn (無視されるシグナル) は 3, 20, 21, 22 に対応する 4 つのシグナルという意味になります。
0000000000380004 (hex) -> 1110000000000000000100 (bit) シグナル名 番号
||| *---------> SIGQUIT (03)
||*--------------------------> SIGTSTP (20)
|*---------------------------> SIGTTIN (21)
*----------------------------> SIGTTOU (22)
なお、ps s
でも同じような情報は確認できます。
$ ps s
UID PID PENDING BLOCKED IGNORED CAUGHT STAT TTY TIME COMMAND
1000 29498 0000000000000000 0000000000010000 0000000000380004 000000004b817efb Ss pts/0 0:00 /bin/bash
1000 29517 0000000000000000 0000000000010000 0000000000380004 000000004b817efb Ss pts/1 0:00 /bin/bash
1000 29572 0000000000000000 0000000000000000 0000000000003000 00000001ef804eff S+ pts/0 0:00 vim kernel/signal.c
1000 29581 0000000000000000 0000000000000000 0000000000000000 0000000000000000 S pts/1 0:00 sleep 100
1000 29588 0000000000000000 0000000000000000 0000000000000000 00000001f3d1fef9 R+ pts/1 0:00 ps s
■ カーネル編 (v5.5)
ここからは、カーネル観点での話です。シグナルを送信してからシグナルアクションが処理されるまでの流れを見ていきます。
まずデータ構造を見ていきます。データ構造とは、カーネル実装で登場する構造体のことを指します。シグナルでは多くの構造体が関連しているため、先に把握しておくことで、コードが理解し易くなると思います。
次にカーネル実装 11 を見ていきます。シグナルは次のようにシグナル生成 (1 段階目)、シグナル配送 (2 段階目) という順に段階的な処理構成となっているため、それぞれ見ていきます。
- シグナル生成 (1 段階目) ... シグナルを送信したことを送信先のプロセスのデータ構造に記録する (シグナルを保留する)
- シグナル配送 (2 段階目) ... シグナルに応じて、シグナルアクションを実行する
1. データ構造
シグナルは、次のとおり、 task_struct、signal_struct、sighand_struct など多くの構造体が関連しています。
- task_struct (プロセスディスクリプタ)
- signal_struct (シグナルディスクリプタ)
- sighand_struct (シグナルハンドラディスクリプタ)
- sigpending
- k_sigaction
- sigaction
- thread_info
- siginfo
- kernel_siginfo
大変混乱しやすいと思いますので、個人的に重要だと思う箇所に関しては、以下のように用途ごとにまとめました。
保留中シグナルを示すフラグ
シグナル生成によって、シグナルを受け取ったプロセスは、シグナル配送によって処理されるまでの間、シグナル保留状態 (シグナル配送待ち) となります。この状態は TIF_SIGPENDING というフラグで示され、スレッドフラグ (flags) に記録されます。
シグナル保留用のキュー (特定プロセス用、スレッドグループ全体用)
受信したシグナルは、sigqueue 構造体によって管理され、関連する幾つかの情報 (※1) を持ちます。それらは保留用のキューに入れられます (sigpending の list につなげられます)。その際、シグナルの送信宛に応じて 2 種類 (特定プロセス用、スレッドグループ全体用) の内のどちらかのキューが使用されます。
また、保留されたシグナルは signal に記録されます。この値は /proc/<PID>/status
から確認できます (前述の「シグナルの状態確認」と後述の「おまけ」参照)。
(※1) 幾つかの情報とは、次の kernel_siginfo 構造体のメンバを指します。
メンバ | 意味 |
---|---|
si_signo | シグナル番号 |
si_code | シグナルの発生元を示すコード (※2) |
si_errno | シグナル発生を発生させることになった命令のエラーコード |
__sifilelds | union のため条件に応じて si_pid (送信先 pid)、si_uid (送信先 uid) などに変化する |
(※2) シグナルの発生元を示すコードには次のような値が入ります。
include/uapi/asm-generic/siginfo.h より抜粋
定義名 | 値 | 意味 |
---|---|---|
SI_USER | 0 | kill()、sigsend()、raise() によるシグナル送信 |
SI_KERNEL | 0x80 | カーネルによるシグナル送信 |
SI_QUEUE | -1 | sigqueue() によるシグナル送信 |
SI_TIMER | -2 | POSIX タイマの時間経過によるシグナル送信 |
SI_MESGQ | -3 | POSIX メッセージキューの状態変化によるシグナル送信 |
SI_ASYNCIO | -4 | 非同期 I/O (AIO) 完了によるシグナル送信 |
SI_SIGIO | -5 | SIGIO のキューイングによるシグナル送信 |
SI_TKILL | -6 | tkill()、tgkill() によるシグナル送信 |
SI_DETHREAD | -7 | sent by execve() killing subsidiary threads 12 |
SI_ASYNCNL | -60 | getaddrinfo_a() での名前検索完了によるシグナル送信 |
なお、上記はシグナル共通の値です。特定のシグナルによっては、次のように別の値が入ることもあります 7。
シグナル名 | si_code に入る値 |
---|---|
SIGBUG | BUS_* |
SIGCHLD | CLD_* |
SIGFPE | FPE_* |
SIGILL | ILL_* |
SIGPOLL/SIGIO | POLL_* |
SIGSEGV | SEGV_* |
SIGTRAP | TRAP_* |
SIGSYS | SYS_SECCOMP |
シグナルブロック情報
sigprocmask() などによってブロックされたシグナル番号は、ブロック情報 (blocked, readl_blocked13) に記録されます。この値は /proc/<PID>/status
から確認できます (前述の「シグナルの状態確認」 と後述の「おまけ」参照)。
シグナルアクション関連の情報
sigaction() などでシグナルアクションの設定を変更すると、シグナルハンドラディスクリプタ (sighand_struct) に幾つかの情報 (※) が格納されます。また、シグナルハンドラディスクリプタの action[x] はシグナル番号 (_NSIG) 個の配列になっているため (action[シグナル番号-1])、シグナル毎に設定されます。
(※) 幾つかの情報とは、次の sigaction 構造体のメンバを指します。
メンバ | 意味 |
---|---|
sa_flags | シグナルの使い方を示す SA_* フラグ7 |
sa_handler | SIG_IGN、SIG_DFL、シグナルハンドラへのポインタなど、シグナルアクションの種類 |
sa_mask | ハンドラ実行時にブロックする (受信を禁止する) シグナル |
2. 実装
シグナルを送信してからシグナルアクションが処理される (kill -TERM 1234
を実行した際の処理) までの流れを見ていきます。
以降、「シグナル生成」と「シグナル配送」で分けてそれぞれ見ていきます (「...」 の部分は省略箇所です)
シグナル生成
この段階での主な仕事は、送信先プロセスのデータ構造に「シグナルを送信した (シグナル配送待ちがある)」ということを送信先プロセスのデータ構造に記録し、通知することです (標準動作といったシグナルアクションはシグナル配送で処理されます)。
以降で行うコードリーディングの要約として、シグナル生成における処理の全体像を図示します。なお、関数の呼び出しが多いため、コードリーディングでは、この中でも特に重要な関数 (コア関数) である __send_signal() から読み進めていきます。
__send_signal()
この関数は、送信されたシグナルを配送するかどうかを決め (prepare_signal())、シグナルを保留用キューに入れます (__sigqueue_alloc(), list_add_tail()) その際、保留シグナルの番号を記録します (sigaddset())。また、配送する場合は、プロセスディスクリプタのスレッドフラグに TIF_SIGPENDING を立て、配送可能なプロセスを起床します (complete_signal())。
詳細は以降をご参照ください。
1065 static int __send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t,
1066 enum pid_type type, bool force)
1067 {
1068 struct sigpending *pending;
1069 struct sigqueue *q;
1070 int override_rlimit;
1071 int ret = 0, result;
....
1076 if (!prepare_signal(sig, t, force))
1077 goto ret;
1076 行目の prepare_signal() では、シグナルを配送する必要があるかどうかををチェックします。たとえば、シグナルハンドラ (t->sighand->action[sig - 1].sa.sa_handler) に SIG_IGN が設定されている場合や、標準動作が無視 (Ign) のシグナルの場合は、配送する必要がないので、ret
ラベルまで飛びます。シグナルがブロックされていた場合は、シグナルを保留する必要があるため、以降の処理を行います。
また、標準動作が停止 (Stop) のシグナルと SIGCONT に対しては以下の処理も行います。
-
標準動作が停止 (Stop) のシグナル
シグナル保留用キュー (※) から SIGCONT を取り除きます。
-
SIGCONT
シグナル保留用キュー (※) から全ての停止系シグナルを取り除き、wake_up_state() で __TASK_STOPPED 状態のスレッドを起床します (プロセスを再開します)。
またプロセス (スレッドグループ) が終了処理中 (SIGNAL_STOP_STOPPED) の場合は、処理が完了したことにして、プロセスディスクリプタのフラグ (flags) に SIGNAL_CLD_CONTINUED と SIGNAL_STOP_CONTINUED を立てます。終了処理中でなくとも group_stop_count がカウントされている場合は、既に終了処理が終わったということなので (おそらく)、プロロセスディスクリプタのフラグ (flags) に SIGNAL_CLD_STOPPED と SIGNAL_STOP_CONTINUED を立てます。
(※) 「データ構造」でも説明しましたが、シグナル保留用キューには、「特定プロセス用 (t->pending)」と「スレッドグループ全体用 (t->signal->shared_pending)」の 2 種類があります。ここでは、その両方のキューから該当するシグナルを取り除きます。
1078
1079 pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending;
1079 行目では、pending に「特定プロセス用」「スレッドグループ全体用」のどちらのシグナル保留用キューを使用するかを決めています。今回は kill コマンド実行により、type に PIDTYPE_TGID が渡されるため、pending には、スレッドグループ全体用の方が使われます。
1080 /*
1081 * Short-circuit ignored signals and support queuing
1082 * exactly one non-rt signal, so that we can get more
1083 * detailed information about the cause of the signal.
1084 */
1085 result = TRACE_SIGNAL_ALREADY_PENDING;
1086 if (legacy_queue(pending, sig))
1087 goto ret;
1086 行目の legacy_queue() では、sig
が「標準シグナル」かつ「既にシグナル保留用キューに存在するなら」 ret
ラベルまで飛びます (標準シグナルは複数の同一のシグナルを受信しても 1 つしか受信できない)。
1088
1089 result = TRACE_SIGNAL_DELIVERED;
1090 /*
1091 * Skip useless siginfo allocation for SIGKILL and kernel threads.
1092 */
1093 if ((sig == SIGKILL) || (t->flags & PF_KTHREAD))
1094 goto out_set;
コメントのとおりですが、シグナルが SIGKILL OR 送信先がカーネルスレッド (PF_KTHREAD フラグ) の場合は、out_set
ラベルまで飛び、以降のシグナル保留キュー登録処理などを行いません。
1096 /*
1097 * Real-time signals must be queued if sent by sigqueue, or
1098 * some other real-time mechanism. It is implementation
1099 * defined whether kill() does so. We attempt to do so, on
1100 * the principle of least surprise, but since kill is not
1101 * allowed to fail with EAGAIN when low on memory we just
1102 * make sure at least one signal gets delivered and don't
1103 * pass on the info struct.
1104 */
1105 if (sig < SIGRTMIN)
1106 override_rlimit = (is_si_special(info) || info->si_code >= 0);
1107 else
1108 override_rlimit = 0;
1109
1105 行目の SIGRTMIN は 32 なので、sig
が標準シグナルがどうかで処理が分岐します。
真 のルート (1106 行目) の is_si_special() では、info
が SEND_SIG_NOINFO (ユーザモードのプロセスからシグナルが送信された) か、SEND_SIGPRIV (カーネルからシグナルが送信された) の場合は 真 を返します。右の info->si_code >=0
(SI_USER か SI_KERNEL) の場合も 真 を返します。
なお、今回は kill コマンド実行により info->si_code
に SI_USER が設定されるため、override_rlimit
には 真 が入ります。
1110 q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
1111 if (q) {
1112 list_add_tail(&q->list, &pending->list);
1113 switch ((unsigned long) info) {
1114 case (unsigned long) SEND_SIG_NOINFO:
1115 clear_siginfo(&q->info);
1116 q->info.si_signo = sig;
1117 q->info.si_errno = 0;
1118 q->info.si_code = SI_USER;
1119 q->info.si_pid = task_tgid_nr_ns(current,
1120 task_active_pid_ns(t));
1121 rcu_read_lock();
1122 q->info.si_uid =
1123 from_kuid_munged(task_cred_xxx(t, user_ns),
1124 current_uid());
1125 rcu_read_unlock();
1126 break;
1127 case (unsigned long) SEND_SIG_PRIV:
1128 clear_siginfo(&q->info);
1129 q->info.si_signo = sig;
1130 q->info.si_errno = 0;
1131 q->info.si_code = SI_KERNEL;
1132 q->info.si_pid = 0;
1133 q->info.si_uid = 0;
1134 break;
1135 default:
1136 copy_siginfo(&q->info, info);
1137 break;
1138 }
....
1110 行目では __sigqueue_alloc() にて、シグナル管理用の sigqueue 型 q
を新しく確保 (アロケーション) するように試みます。この関数の内部処理では、送信先プロセスの所有者 (ユーザ) が保留するシグナルの数が上限を超えないかどうかをチェックし、超えない場合にアロケーションを試みます。ただし、override_rlimit
が 真 なら上限チェックをせずにアロケーションを試みます。
1112 行目から 1138 行目はアロケーション成功時の処理です。
1112 行目の list_add_tail() では、シグナル保留用キューにアロケーションした q
を登録し (リストへつなぎ)、1113 行目以降の処理では、引数 info
に応じて q
へシグナルの情報を格納していきます。
1157 out_set:
....
1159 sigaddset(&pending->signal, sig);
....
シグナル保留用キューのシグナルセットに現在のシグナル番号を追加します (前述の「シグナルの状態確認」と「データ構造」参照)。
1175 complete_signal(sig, t, type);
1176 ret:
1177 trace_signal_generate(sig, info, t, type != PIDTYPE_PID, result);
1178 return ret;
1179 }
1175 行目の complete_signal() については後述します。
1177 行目の trace_signal_generate() は TRACE_EVENT マクロで signal_generate という名前のトレースポイントを作成しているようです。これは、次のようなカーネルトレース情報の出力に利用 (レポート) されています。
kill-5371 [003] 1058202.036613: signal_generate: sig=15 errno=0 code=0 comm=sleep pid 5359 grp=1 res=0
complete_signal()
この関数は、送信されたシグナルを __send_signal() で配送すると決めた場合に呼び出されます。
そして、この関数でシグナル配送の準備ができているプロセスを探します。見つけたら該当プロセスのスレッドフラグに TIF_SIGPENDING を立て、起床します (通知します)。
詳細は以降をご参照ください。
984 static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
985 {
986 struct signal_struct *signal = p->signal;
987 struct task_struct *t;
988
989 /*
990 * Now find a thread we can wake up to take the signal off the queue.
991 *
992 * If the main thread wants the signal, it gets first crack.
993 * Probably the least surprising to the average bear.
994 */
995 if (wants_signal(sig, p))
996 t = p;
...
995 行目の wants_signal() では、次のパターンでシグナル配送可能 (準備ができている) なプロセスを探します。
-
シグナルがブロックされてない (SIGKILL, SIGSTOP はブロック不可) AND
- プロセスが終了処理中でない (p->flags に PF_ EXITING が無い) AND
- シグナル (
sig
) が SIGKILL (プロセスの状態やフラグ有無は見ないで強制する)
- シグナル (
- プロセスが終了処理中でない (p->flags に PF_ EXITING が無い) AND
-
シグナルがブロックされてない (SIGKILL, SIGSTOP はブロック不可) AND
- プロセスが終了処理中でない (p->flags に PF_ EXITING が無い) AND
- プロセスの状態 (p->state) が一時停止中 (__TASK_STOPPED) か、デバッガなどにより停止中 (__TASK_TRACED) でない AND
- プロセスが CPU 上で実行中 (カレントプロセス) OR プロセスのスレッドフラグ (p->thread_info->flags) に TIF_SIGPENDING が無い
- プロセスの状態 (p->state) が一時停止中 (__TASK_STOPPED) か、デバッガなどにより停止中 (__TASK_TRACED) でない AND
- プロセスが終了処理中でない (p->flags に PF_ EXITING が無い) AND
1021 /*
1022 * Found a killable thread. If the signal will be fatal,
1023 * then start taking the whole group down immediately.
1024 */
1025 if (sig_fatal(p, sig) &&
1026 !(signal->flags & SIGNAL_GROUP_EXIT) &&
1027 !sigismember(&t->real_blocked, sig) &&
1028 (sig == SIGKILL || !p->ptrace)) {
コメントのとおり、Kill 可能なスレッドを探します。次の条件を全て満たす場合、1039 行目から 1042 行目の処理を行います。
-
sig_fatal() が 真 (※) AND
- スレッドグループが終了中でない (SIGNAL_GROUP_EXIT は「スレッドグループが終了中」というフラグ) AND
- シグナルがブロックされていない (sigismember() は、t->real_blocked (ブロック情報) に sig が含まれていれば 真) AND
- シグナルが SIGKILL OR ptrace 関連フラグ (p->ptrace) がない時
- シグナルがブロックされていない (sigismember() は、t->real_blocked (ブロック情報) に sig が含まれていれば 真) AND
- スレッドグループが終了中でない (SIGNAL_GROUP_EXIT は「スレッドグループが終了中」というフラグ) AND
(※) sig_fatal() は次の 2 パターンで 真 を返します。
-
シグナルが標準シグナル AND
- 標準動作が無視 (Ign) / 停止 (Stop) のシグナルでない AND
- シグナルアクションが SIG_DFL
- 標準動作が無視 (Ign) / 停止 (Stop) のシグナルでない AND
-
シグナルがリアルタイムシグナル AND
- シグナルアクションが SIG_DFL
1029 /*
1030 * This signal will be fatal to the whole group.
1031 */
1032 if (!sig_kernel_coredump(sig)) {
sig_kernel_coredump() は、標準動作がコアダンプ (Core) のシグナルでない時 真 を返します。
1033 /*
1034 * Start a group exit and wake everybody up.
1035 * This way we don't have other threads
1036 * running and doing things after a slower
1037 * thread has the fatal signal pending.
1038 */
1039 signal->flags = SIGNAL_GROUP_EXIT;
1040 signal->group_exit_code = sig;
1041 signal->group_stop_count = 0;
1042 t = p;
1043 do {
1044 task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
1045 sigaddset(&t->pending.signal, SIGKILL);
1046 signal_wake_up(t, 1);
1047 } while_each_thread(p, t);
1048 return;
1049 }
1050 }
1039 行目から 1042 行目までの処理を行った後は、do-while でスレッドグループ内を走査し終わるまで、次に示す処理をループします。
- 1044 行目の task_clear_jobctl_pending() で、保留中のジョブコントロールフラグ (t->jobctl) をクリア
- 1045 行目の sigaddset() で、シグナル保留用キューのシグナルセットに SIGKILL (番号) を追加
- 1046 行目の signal_wake_up() で、スレッドフラグ (t->flags) に TIF_SIGPENDING を立て、TASK_WAKEKILL (SIGKILL などの重要なシグナルを受信可能) か TASK_INTERRUPTIBLE (何らかのイベント待ち) のプロセスを起床 (通知)
ループを抜けると、この関数での処理を終了します。
1052 /*
1053 * The signal is already in the shared-pending queue.
1054 * Tell the chosen thread to wake up and dequeue it.
1055 */
1056 signal_wake_up(t, sig == SIGKILL);
1057 return;
1058 }
ここまで到達できた場合、signal_wake_up() で、スレッドフラグ (t->flags) に TIF_SIGPENDING を立て、TASK_INTERRUPTIBLE 状態のプロセスを起床します (保留シグナルを通知)。また、シグナルが SIGKILL の場合は TASK_WAKEKILL 状態のプロセスも起床します。
以上で、「シグナル生成」の処理は終わりです。続いて、「シグナル配送」を見ていきます。
シグナル配送
この段階での主な仕事は、シグナルハンドラや標準動作などのシグナルアクションを実行することです。ただし、この処理はプロセスが保留シグナルを持つ (前述の「シグナル生成」で説明した TIF_SIGPENDING フラグを持つ) 場合に限ります。
以降で行うコードリーディングの要約として、シグナル配送における処理の全体像を図示します。なお、シグナル配送は一部アーキテクチャに依存するコードがありますが、ここでは x86 (arch/x86 配下) のコードを見ていきます。
exit_to_usermode_loop()
この関数は、カーネルモードからユーザモードへ戻る度 (割り込み/例外ハンドラ、システコール処理後など) に呼び出され、スレッドフラグをチェックします。その際、TIF_SIGPENDING があると、do_signal() を呼びだします (160 行目)。
詳細は以降をご参照ください。
arch/x86/entry/common.c (L136)
136 static void exit_to_usermode_loop(struct pt_regs *regs, u32 cached_flags)
137 {
138 /*
139 * In order to return to user mode, we need to have IRQs off with
140 * none of EXIT_TO_USERMODE_LOOP_FLAGS set. Several of these flags
141 * can be set at any time on preemptible kernels if we have IRQs on,
142 * so we need to loop. Disabling preemption wouldn't help: doing the
143 * work to clear some of the flags can sleep.
144 */
145 while (true) {
146 /* We have work to do. */
147 local_irq_enable();
...
158 /* deal with pending signal delivery */
159 if (cached_flags & _TIF_SIGPENDING)
160 do_signal(regs);
...
171 /* Disable IRQs and retry */
172 local_irq_disable();
173
174 cached_flags = READ_ONCE(current_thread_info()->flags);
175
176 if (!(cached_flags & EXIT_TO_USERMODE_LOOP_FLAGS))
177 break;
178 }
179 }
do_signal()
この関数は、標準動作の実行 (get_signal()) やシグナルハンドラの実行 (handle_signal()) を行います。また、必要に応じてシステコールの再実行や特定のシステムコールによって一時的に書き換えられたシグナルブロック情報の復元を行います。
詳細は以降をご参照ください。
arch/x86/kernel/signal.c (L806)
806 /*
807 * Note that 'init' is a special process: it doesn't get signals it doesn't
808 * want to handle. Thus you cannot kill init even with a SIGKILL even by
809 * mistake.
810 */
811 void do_signal(struct pt_regs *regs)
812 {
813 struct ksignal ksig;
814
815 if (get_signal(&ksig)) {
816 /* Whee! Actually deliver the signal. */
817 handle_signal(&ksig, regs);
818 return;
819 }
820
815 行目の get_signal() では標準動作の実行と保留用キューからのシグナル取り出しを行います。その結果が 真 (取り出せたら) なら handle_signal() を実行しシグナルハンドラの実行を行います。偽 (取り出せなかった) なら以降の処理を行います (get_signal()、handle_signal() は重要な関数なので詳細は後述します)。
821 /* Did we come from a system call? */
822 if (syscall_get_nr(current, regs) >= 0) {
823 /* Restart the system call - no handlers present */
824 switch (syscall_get_error(current, regs)) {
825 case -ERESTARTNOHAND:
826 case -ERESTARTSYS:
827 case -ERESTARTNOINTR:
828 regs->ax = regs->orig_ax;
829 regs->ip -= 2;
830 break;
831
832 case -ERESTART_RESTARTBLOCK:
833 regs->ax = get_nr_restart_syscall(regs);
834 regs->ip -= 2;
835 break;
836 }
837 }
ここはシステムコールの再実行に関する処理です (システムコールの処理中にシグナルを受信した場合、エラーを吐いて処理を中断することがあるため、このような処理があります)。
822 行目の syscall_get_nr() は、ユーザモードのレジスタ (regs
) からシステムコール番号を取得します。それが 0 以上だったら、824 行目の syscall_get_erro() でエラー番号を取得し、そのエラー番号に応じてカーネルがシステムコールを再実行します。
なお、システムコールの再実行については「Linuxのシステムコール再実行について」という記事に詳しく書かれていました。regs->ip -= 2
の意味など詳細はこちらをご参照ください。
838
839 /*
840 * If there's no signal to deliver, we just put the saved sigmask
841 * back.
842 */
843 restore_saved_sigmask();
844 }
restore_saved_sigmask() では、ppoll、pselect、epoll、sigsuspend などのシステムコールによって一時的に書き換えられたシグナルブロック情報 (task_struct->blocked) を、あらかじめ退避させていたシグナルブロック情報 (task_struct->saved_sigmask) から復元します。
get_signal()
この関数は、シグナル保留用キューからシグナルを取り出し、シグナルに応じて標準動作の実行を行います。
詳細は以降をご参照ください。
2521 bool get_signal(struct ksignal *ksig)
2522 {
2523 struct sighand_struct *sighand = current->sighand;
2524 struct signal_struct *signal = current->signal;
2525 int signr;
....
2540 relock:
....
2542 /*
2543 * Every stopped thread goes here after wakeup. Check to see if
2544 * we should notify the parent, prepare_signal(SIGCONT) encodes
2545 * the CLD_ si_code into SIGNAL_CLD_MASK bits.
2546 */
2547 if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
2548 int why;
2549
2550 if (signal->flags & SIGNAL_CLD_CONTINUED)
2551 why = CLD_CONTINUED;
2552 else
2553 why = CLD_STOPPED;
2554
2555 signal->flags &= ~SIGNAL_CLD_MASK;
2556
2557 spin_unlock_irq(&sighand->siglock);
2558
2559 /*
2560 * Notify the parent that we're continuing. This event is
2561 * always per-process and doesn't make whole lot of sense
2562 * for ptracers, who shouldn't consume the state via
2563 * wait(2) either, but, for backward compatibility, notify
2564 * the ptracer of the group leader too unless it's gonna be
2565 * a duplicate.
2566 */
2567 read_lock(&tasklist_lock);
2568 do_notify_parent_cldstop(current, false, why);
2569
2570 if (ptrace_reparented(current->group_leader))
2571 do_notify_parent_cldstop(current->group_leader,
2572 true, why);
2573 read_unlock(&tasklist_lock);
2574
2575 goto relock;
2576 }
....
2543 行目のコメントにも少し書かれていますが、2547 行目のフラグ (SIGNAL_CLD_MASK == SIGNAL_CLD_STOPPED or SIGNAL_CLD_CONTINUED )は、シグナル生成 (__send_signal()) で呼び出される prepare_signal() で SIGCONT の処理をするときに設定されるます。このフラグが設定されている場合は、do_notify_parent_cldstop() で親にシグナル (SIGCHLD) を送信します。
2588 for (;;) {
2589 struct k_sigaction *ka;
....
2616 /*
2617 * Signals generated by the execution of an instruction
2618 * need to be delivered before any other pending signals
2619 * so that the instruction pointer in the signal stack
2620 * frame points to the faulting instruction.
2621 */
2622 signr = dequeue_synchronous_signal(&ksig->info);
2623 if (!signr)
2624 signr = dequeue_signal(current, ¤t->blocked, &ksig->info);
2625
2626 if (!signr)
2627 break; /* will return 0 */
....
2622 行目の dequeue_synchronous_signal() で同期シグナル (SIG[SEGV|BUS|ILL|TRAP|FPE|SYS]) をキューから取り出します。なければ、2624 行目の dequeue_signal() で非同期シグナルをキューから取り出します。それでもなければ、break でループを抜けこの関数での処理を終了します。
2635 ka = &sighand->action[signr-1];
2636
2637 /* Trace actually delivered signals. */
2638 trace_signal_deliver(signr, &ksig->info, ka);
2638 行目の trace_signal_deliver() は TRACE_EVENT マクロで signal_deliver という名前のトレースポイントを作成するようです。これは、次のようなカーネルトレース情報の出力に利用 (レポート) されています。定義元のコメントを見るに、ここに到達できた時点でシグナルが配送されたことになるようです。
trace-cmd-17636 [001] 607498.455844: signal_deliver: sig=2 errno=0 code=128 sa_handler=5567b90d1540 sa_flags=1400000
以降は、シグナル無視、シグナルハンドラ、標準動作などのシグナルアクションに関する処理です。ka
(2635 行目) の内容により分岐します。
2639
2640 if (ka->sa.sa_handler == SIG_IGN) /* Do nothing. */
2641 continue;
C 言語 API のsigaction() などでシグナルアクションに無視 (SIG_IGN) を設定していた場合は、何もしません。
2642 if (ka->sa.sa_handler != SIG_DFL) {
2643 /* Run the handler. */
2644 ksig->ka = *ka;
2645
2646 if (ka->sa.sa_flags & SA_ONESHOT)
2647 ka->sa.sa_handler = SIG_DFL;
2648
2649 break; /* will return non-zero "signr" value */
2650 }
2642 行目は、SIG_DFL でない場合 (つまり、シグナルハンドラを設定していた場合) 、2644 行目で ksig->ka
に ka
のアドレスを入れます。これは get_signal() を抜けたあとの handle_signal() でユーザプロス用のレジスタに設定する時に使用します。
2646 行目の SA_ONESHOT (sigaction() で設定可能なフラグ) はシグナルハンドラ実行後にシグナルアクションをデフォルト (SIG_DFL) に戻すという意味です。つまり、シグナルハンドラの実行するのは、初回のシグナル受信時だけです。
最後に break でループを抜け、この関数での処理を終えます。この場合 signr
には取り出したシグナル番号が入っているはずなので、後述の handle_signal() が実行されます。
2651
2652 /*
2653 * Now we are doing the default action for this signal.
2654 */
2655 if (sig_kernel_ignore(signr)) /* Default is nothing. */
2656 continue;
....
ここからは、標準動作の実行に関する処理です。
2655 行目の sig_kernel_ignore() は signr
の標準動作が無視 (Ign) なら 真 を返します。その場合、何もしません。
2671
2672 if (sig_kernel_stop(signr)) {
2673 /*
2674 * The default action is to stop all threads in
2675 * the thread group. The job control signals
2676 * do nothing in an orphaned pgrp, but SIGSTOP
2677 * always works. Note that siglock needs to be
2678 * dropped during the call to is_orphaned_pgrp()
2679 * because of lock ordering with tasklist_lock.
2680 * This allows an intervening SIGCONT to be posted.
2681 * We need to check for that and bail out if necessary.
2682 */
2683 if (signr != SIGSTOP) {
2684 spin_unlock_irq(&sighand->siglock);
2685
2686 /* signals can be posted during this window */
2687
2688 if (is_current_pgrp_orphaned())
2689 goto relock;
2690
2691 spin_lock_irq(&sighand->siglock);
2692 }
2693
2694 if (likely(do_signal_stop(ksig->info.si_signo))) {
2695 /* It released the siglock. */
2696 goto relock;
2697 }
2698
2699 /*
2700 * We didn't actually stop, due to a race
2701 * with SIGCONT or something like that.
2702 */
2703 continue;
2704 }
2672 行目の sig_kernel_stop() は signr
の標準動作がプロセス一時停止 (Stop) のシグナルなら 真 を返します。真 なら、2694 行目の do_signal_stop() でプロセスディスクリプタのフラグ (flags) に SIGNAL_STOP_STOPPED を立て、プロセスの一時停止 (TASK_STOPPED) を行います。
ただし、シグナルが SIGSTOP 以外かつ、プロセスグループが孤児 (親なし) の 場合は do_signal_stop() を実行しません (一時停止しません)。
2705
2706 fatal:
2707 spin_unlock_irq(&sighand->siglock);
....
2711 /*
2712 * Anything else is fatal, maybe with a core dump.
2713 */
2714 current->flags |= PF_SIGNALED;
2715
2716 if (sig_kernel_coredump(signr)) {
2717 if (print_fatal_signals)
2718 print_fatal_signal(ksig->info.si_signo);
2719 proc_coredump_connector(current);
2720 /*
2721 * If it was able to dump core, this kills all
2722 * other threads in the group and synchronizes with
2723 * their demise. If we lost the race with another
2724 * thread getting here, it set group_exit_code
2725 * first and our do_group_exit call below will use
2726 * that value and ignore the one we pass it.
2727 */
2728 do_coredump(&ksig->info);
2729 }
....
2716 行目の sig_kernel_coredump() は signr
の標準動作がコアダンプ (Core) なら 真 を返します。真 なら 2728 行目の do_coredump() でシグナルディスクリプタのフラグ (flags) に SIGNAL_GROUP_COREDUMP を立て、コアダンプ生成処理を行います。
2731 /*
2732 * Death signals, no core dump.
2733 */
2734 do_group_exit(ksig->info.si_signo);
2735 /* NOTREACHED */
2736 }
signr
の標準動作がプロセス終了 (Term) やコアダンプ (Core) の場合、2724 行目の do_group_exit() でシグナルディスクリプタのフラグ (flags) に SIGNAL_GROUP_EXIT を立て、プロセス (スレッドグループ) の終了を行います。
なお、2736 行目の }
は 2588 行目の for (;;) {
の対になる部分です。このループは、2627 行目の break か 2649 行目の break で抜けることができます。前者はシグナル保留用キューから取り出すものが無くなった場合で、後者はシグナルアクション (sa_handler) にシグナルハンドラが設定されていた場合です。
2737 spin_unlock_irq(&sighand->siglock);
2738
2739 ksig->sig = signr;
2740 return ksig->sig > 0;
2741 }
最後にキューから取り出したシグナル番号が 0 より大きいかどうかを判定します。大きい場合はシグナルを取り出せたということで 真 を返し、次の handle_signal() へ進みます。
handle_signal()
この関数は、システムコールの再実行 (必要なら) と、シグナルハンドラ実行用にユーザモード時のスタックフレームを設定します。
arch/x86/kernel/signal.c (L71)
710 static void
711 handle_signal(struct ksignal *ksig, struct pt_regs *regs)
712 {
713 bool stepping, failed;
714 struct fpu *fpu = ¤t->thread.fpu;
715
...
719 /* Are we from a system call? */
720 if (syscall_get_nr(current, regs) >= 0) {
721 /* If so, check system call restarting.. */
722 switch (syscall_get_error(current, regs)) {
723 case -ERESTART_RESTARTBLOCK:
724 case -ERESTARTNOHAND:
725 regs->ax = -EINTR;
726 break;
727
728 case -ERESTARTSYS:
729 if (!(ksig->ka.sa.sa_flags & SA_RESTART)) {
730 regs->ax = -EINTR;
731 break;
732 }
733 /* fallthrough */
734 case -ERESTARTNOINTR:
735 regs->ax = regs->orig_ax;
736 regs->ip -= 2;
737 break;
738 }
739 }
740
...
do_signal() でも出ましたが14、ここにもシステムコールの再実行に関する処理があります。ただし、カーネルがシステムコールを再実行するケースは次の 2 パターンのみです。
- エラーが -ERESTARTSYS (728 行目) かつ sa_flags に SA_RESTART (sigaction() で設定可能なフラグ) が立っている場合
- エラーが 734 行目の -ERESTARTNOINTR (734 行目) だった場合
それ以外の場合、カーネルはシステムコールを再実行せず regs->ax
に -EINTR (関数呼び出しが割り込まれたというエラー) を設定します。システムコールを再実行するかどうかは、このエラーからユーザ (ユーザプログラムの条件) が判断します。
750 failed = (setup_rt_frame(ksig, regs) < 0);
...
751 if (!failed) {
752 /*
753 * Clear the direction flag as per the ABI for function entry.
754 *
755 * Clear RF when entering the signal handler, because
756 * it might disable possible debug exception from the
757 * signal handler.
758 *
759 * Clear TF for the case when it wasn't set by debugger to
760 * avoid the recursive send_sigtrap() in SIGTRAP handler.
761 */
762 regs->flags &= ~(X86_EFLAGS_DF|X86_EFLAGS_RF|X86_EFLAGS_TF);
763 /*
764 * Ensure the signal handler starts with the new fpu state.
765 */
766 fpu__clear(fpu);
767 }
768 signal_setup_done(failed, ksig, stepping);
769 }
750 行目の setup_rt_frame() では、シグナルハンドラ実行用にユーザモード時のスタックフレームを設定します。スタックフレームには、シグナルハンドラ実行に必要な情報 (シグナル番号、siginfo 構造体 の情報や rt_sigreturn() システムコールをユーザモードプロセスから呼び出すための情報が格納されています。rt_sigreturn() はシグナルハンドラ実行後にカーネルモードへ復帰するのに必要です。
また、setup_rt_frame() での設定が成功した場合は、762、766 行目でフラグ (regs->flags
) や FPU state (浮動小数点数レジスタ) をクリアします (理由は不明)。
そして、最後 (768 行目) の signal_setup_done() では setup_rt_frame() での設定成否で処理が分岐します。
-
成功した場合:
sigorsets() でカレントプロセスのブロック情報 (current->blocked) と sigaction() でユーザが設定したブロック情報 (sigaction->sa_mask) の論理和 (OR) を取り、それをカレントプロセスのブロック情報に設定します。また、スレッドフラグに TIF_SINGLESTEP (デバッガで使用) が設定されていた場合は、ptrace_notify(SIGTRAP) を実行します。 -
失敗した場合:
force_sigsegv() でカレントプロセスに SIGSEGV を送信します。
以上で、「シグナル配送」の処理は終わりです。
3. おまけ
/proc/<PID>/status
にあるシグナル関連のコード
/proc/<PID>/status
にある SigPnd、ShdPnd、などの値はどこに由来するのかコード上から探してみました。
task_sig() に答えがありました。たとえば、SigBlk (blocked
) には 283 行目で p->blocked
を入れています。
266 static inline void task_sig(struct seq_file *m, struct task_struct *p)
267 {
268 unsigned long flags;
269 sigset_t pending, shpending, blocked, ignored, caught;
270 int num_threads = 0;
271 unsigned int qsize = 0;
272 unsigned long qlim = 0;
273
274 sigemptyset(&pending);
275 sigemptyset(&shpending);
276 sigemptyset(&blocked);
277 sigemptyset(&ignored);
278 sigemptyset(&caught);
279
280 if (lock_task_sighand(p, &flags)) {
281 pending = p->pending.signal;
282 shpending = p->signal->shared_pending.signal;
283 blocked = p->blocked;
284 collect_sigign_sigcatch(p, &ignored, &caught);
285 num_threads = get_nr_threads(p);
286 rcu_read_lock(); /* FIXME: is this correct? */
287 qsize = atomic_read(&__task_cred(p)->user->sigpending);
288 rcu_read_unlock();
289 qlim = task_rlimit(p, RLIMIT_SIGPENDING);
290 unlock_task_sighand(p, &flags);
291 }
292
293 seq_put_decimal_ull(m, "Threads:\t", num_threads);
294 seq_put_decimal_ull(m, "\nSigQ:\t", qsize);
295 seq_put_decimal_ull(m, "/", qlim);
296
297 /* render them all */
298 render_sigset_t(m, "\nSigPnd:\t", &pending);
299 render_sigset_t(m, "ShdPnd:\t", &shpending);
300 render_sigset_t(m, "SigBlk:\t", &blocked);
301 render_sigset_t(m, "SigIgn:\t", &ignored);
302 render_sigset_t(m, "SigCgt:\t", &caught);
303 }
SIGKILL、SIGSTOP がブロックできない理由
SIGKILL と SIGSTOP がブロックできない理由をコード上から探してみました。
シグナルブロックに使う sigprocmask() 実行時に呼び出される sys_rt_sigprocmask() に答えがありました。3025 行目の sigdelsetmask() で新しく設定したブロック情報 (new_set
) から SIGKILL と SIGSTOP を削除しています (つまり、SIGKILL と SIGSTOP をブロックするように設定しても実際は設定されない)。
3003 /**
3004 * sys_rt_sigprocmask - change the list of currently blocked signals
3005 * @how: whether to add, remove, or set signals
3006 * @nset: stores pending signals
3007 * @oset: previous value of signal mask if non-null
3008 * @sigsetsize: size of sigset_t type
3009 */
3010 SYSCALL_DEFINE4(rt_sigprocmask, int, how, sigset_t __user *, nset,
3011 sigset_t __user *, oset, size_t, sigsetsize)
3012 {
3013 sigset_t old_set, new_set;
3014 int error;
....
3022 if (nset) {
3023 if (copy_from_user(&new_set, nset, sizeof(sigset_t)))
3024 return -EFAULT;
3025 sigdelsetmask(&new_set, sigmask(SIGKILL)|sigmask(SIGSTOP));
3026
3027 error = sigprocmask(how, &new_set, NULL);
3028 if (error)
3029 return error;
3030 }
....
3037 return 0;
3038 }
また、シグナルブロックは sigaction() の sa_mask でも設定できますので、こちらも見てみます。
sigaction() の場合は sys_rt_sigaction() -> do_sigaction() の 3967、3968 行目で同様の処理を行っていました。
3949 int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
3950 {
3951 struct task_struct *p = current, *t;
3952 struct k_sigaction *k;
3953 sigset_t mask;
....
3966 if (act) {
3967 sigdelsetmask(&act->sa.sa_mask,
3968 sigmask(SIGKILL) | sigmask(SIGSTOP));
....
3988 }
....
3991 return 0;
3992 }
SIGKILL、SIGSTOP を無視 or シグナルハンドラを設定できない理由
SIGKILL、SIGSTOP が無視 or 動作変更できない理由もコード上から調べてみました。
sigaction() 実行時に呼び出される sys_rt_sigaction() -> do_sigaction() の 3955 行目に答えがありました。一番右の条件で、act
が有効値かつ sig_kernel_only() が 真 (シグナルが SIGKILL か SIGSTOP の場合) の時に -EINVAL でエラー (引数が無効) を返しています。つまり、sigaction() に SIGKILL および SIGSTOP を渡すと -EINVAL で失敗します。この場合は当然ですが、無視 (SIG_IGN) もシグナルハンドラ (ユーザ定義の関数) も設定できないということになります。
3949 int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact)
3950 {
3951 struct task_struct *p = current, *t;
3952 struct k_sigaction *k;
3953 sigset_t mask;
3954
3955 if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig)))
3956 return -EINVAL;
....
3966 if (act) {
....
3969 *k = *act;
3970 /*
3971 * POSIX 3.3.1.3:
3972 * "Setting a signal action to SIG_IGN for a signal that is
3973 * pending shall cause the pending signal to be discarded,
3974 * whether or not it is blocked."
3975 *
3976 * "Setting a signal action to SIG_DFL for a signal that is
3977 * pending and whose default action is to ignore the signal
3978 * (for example, SIGCHLD), shall cause the pending signal to
3979 * be discarded, whether or not it is blocked"
3980 */
3981 if (sig_handler_ignored(sig_handler(p, sig), sig)) {
3982 sigemptyset(&mask);
3983 sigaddset(&mask, sig);
3984 flush_sigqueue_mask(&mask, &p->signal->shared_pending);
3985 for_each_thread(p, t)
3986 flush_sigqueue_mask(&mask, &t->pending);
3987 }
3988 }
....
3991 return 0;
3992 }
■ 参考文献
- Linux システムプログラミング (9 章シグナル)
- Linux プログラミングインタフェース (20 章シグナル: 基礎、21 章シグナル: シグナルハンドラ, 22 章シグナル: 応用)
- 詳解 Linux カーネル 第3版 (11 章シグナル)
- Linux カーネル解読室 (8 章 シグナル処理)
- Mastering Linux Kernel Development (Chapter 3: Signal Management)
- kernel source code (v5.5)
- Using the TRACE_EVENT() macro (Part 1)
- TASK_KILLABLE: Linux での新しいプロセスの状態
man 1 bash
man 2 setrlimit
man 2 seccomp
man 2 signal
man 2 sigaction
man 3 getaddrinfo_a
man 5 proc
man 7 signal
man 7 pipe
-
詳細は
man 5 core
をご参照ください。 ↩ -
詳細は
man 7 pipe
ご参照ください。 ↩ -
詳細は
man 2 seccomp
ご参照ください。 ↩ -
詳細は
man 1 bash
をご参照ください。 ↩ -
詳細は
man 2 signal
をご参照ください。 ↩ -
シグナル無視の場合も解除はできますが、シグナル無視の状態で受信したシグナルは保留されないため、解除された後も処理されません。無視を解除した上で再度シグナルを受信する必要があります。 ↩
-
詳細は
man 2 sigprocmask
をご参照ください。 ↩ -
タイトルに記載のとおり、カーネルバージョンは調査時点での最新である v5.5 を使用します。 ↩
-
man 2 execve
カーネルソースなどを調べましたが、この原文以上の情報が見当たらず、意味がよく理解できなかったため、原文のまま記載します。 ↩ -
real_blocked は、rt_sigtimedwait(), rt_sigtimedwait_time32(), rt_sigtimedwait_time64() システムコール使用時に設定されるようです。 ↩
-
do_signal() でのシステムコール再実行は、handle_signal() を実行しない場合に通るパス (get_signal() が 偽 の時) なので、ここでのシステムコール再実行処理とは重複しません。 ↩