my_knowledge.ko

Linux | Debug, Analyze, Trace | Tech | etc...

Linux シグナルの基本と仕組み (カーネル v5.5 時点)

この記事は、Linux シグナルの基本と仕組み (カーネルでの実装) について調査したことのまとめです。

シグナルは普段から利用しているものの仕組みについては理解してなかったので、カーネル勉強の題材として調べてみました。想像以上に複雑でボリュームがあったため、書き切れていない部分 (調査しきれなかった部分) もありますが、一通りの流れ (仕組み) は理解できたと思います。

なお、この記事は主に「■ 基本編」と「■ カーネル編 (v5.5)」で構成されています。仕組みを理解するには基本も知る必要があると思い、このような構成となっています。書籍レベルの基本を理解されている方は 「■ カーネル編 (v5.5)」 から読み進めることを推奨します。

■ 基本編

はじめにシグナルの基本について、ざっと整理します。

なお、例で登場するコマンドや API (C 言語) の細かい使い方やエラー処理などは省きます。詳細は man や参考文献の情報等をご活用ください。

1. シグナルとは

プロセスやプロセスグループへ様々なイベントを通知するためにあるカーネルの機能です (ソフトウェア割り込み)。イベントの通知は様々な場所 (自分/他プロセス、カーネル) から行うことが可能で、次のようなことができます。

  • ハングしたプロセスにシグナルを送信して強制終了させる
  • シグナルを送信してプロセスの処理を一時停止・再開させる
  • ハードウェア例外 (0 除算、メモリアクセス違反など) 時にシグナルを送信してプロセスを終了させる
  • シグナルを送信する特殊なキー (Ctrl + C など) を入力しプロセスを終了させる
  • シグナル受信時にユーザ定義の関数 (シグナルハンドラ) を実行させる

なお、シグナルは次のような流れで処理されます。詳細は追々見ていきます。

f:id:Kernel_OGSun:20200614161645j:plain

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 つのみ受信 全て受信
複数の同じシグナル
送信時の順序
規定なし 送信された順に到着
複数の異なるシグナル
送信時の順序
規定なし シグナル番号の小さい順に到着

なお、本稿ではこれ以上深くリアルタイムシグナルについては言及しません [^7signal]。

5. シグナルアクション

シグナル受信時の動作 (シグナルアクション) には、次の 3 種類があります。この動作は後述の sigaction() などから変更できます。ただし、SIGKILL と SIGSTOP は標準動作以外にはできません。

f:id:Kernel_OGSun:20200524175145j:plain

  • 無視

    シグナルを受信しても何もしません。

    (設定するには、後述の 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 プロセス終了とコアダンプ生成 1
Stop プロセス一時停止 (TASK_STOPPED 状態に遷移させる)
Cont 一時停止したプロセスの再開 (TASK_STOPPED 状態からの復帰)

ただし、これは標準シグナルに限ります。リアルタイムシグナルの場合は Term のみです (次図参照)。

f:id:Kernel_OGSun:20200524175203j:plain

標準シグナルと対応する標準動作

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 パイプ破壊 (読み手の無いパイプへの書き出し) 1
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) を超えた 1
SIGXFSZ 25 Core ファイルサイズの上限 (RLIMIT_FSIZE) を超えた [^2setrlimit]。
SIGVTALRM 26 Term 仮想タイマ (プロセスのユーザモードでの CPU 時間) がタイムアウトした
SIGPROF 27 Term プロファイリングタイマがタイムアウトした
SIGWINCH 28 Ign 制御端末のウィンドウサイズが変更された
SIGIO 29 Term 非同期 I/O イベント
SIGPWR 30 Term 電源の異常
SIGSYS 31 Core 不正なシステムコールを実行した 1
シグナルアクションの変更例

SIGKILLSIGSTOP 以外のシグナルアクションを変更するには、trap コマンド や sigaction() などの API (C 言語) を使用します。

trap コマンド

trap は Bash に組み込まれたビルトインコマンドの一種で、シグナルアクションを設定できます 2

ここでは、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 言語) です 3

次のサンプルプログラム (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)。特別な理由が無い限りは使用を避けた方が良いと思います 4

6. シグナルブロック

SIGKILL と SIGSTOP 以外のシグナルは、プロセス毎にブロックすることができます。

たとえば、あるプロセスが SIGINT を受信した場合、通常は標準動作によりプロセスが終了させられますが、SIGINT をブロックしていた場合は、SIGINT を受信しても、シグナル無視のように無反応となります。

シグナル無視との違いは、受信したシグナルを保留するか否かです。シグナル無視の場合はシグナルを保留しませんが、シグナルブロックの場合は保留できます。そのため、ブロックが解除されると、再度シグナルを受信する必要なく処理できます 5

f:id:Kernel_OGSun:20200524175003j:plain

シグナルブロックの設定例

シグナルブロックを設定するには sigprocmask() などの API (C 言語) を使用します 6

次のサンプルプログラム (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)

ここからは、カーネル観点での話です。シグナルを送信してからシグナルアクションが処理されるまでの流れを見ていきます。

まずデータ構造を見ていきます。データ構造とは、カーネル実装で登場する構造体のことを指します。シグナルでは多くの構造体が関連しているため、先に把握しておくことで、コードが理解し易くなると思います。

次にカーネル実装 7 を見ていきます。シグナルは次のようにシグナル生成 (1 段階目)、シグナル配送 (2 段階目) という順に段階的な処理構成となっているため、それぞれ見ていきます。

  • シグナル生成 (1 段階目) ... シグナルを送信したことを送信先のプロセスのデータ構造に記録する (シグナルを保留する)
  • シグナル配送 (2 段階目) ... シグナルに応じて、シグナルアクションを実行する

1. データ構造

シグナルは、次のとおり、 task_struct、signal_struct、sighand_struct など多くの構造体が関連しています。

大変混乱しやすいと思いますので、個人的に重要だと思う箇所に関しては、以下のように用途ごとにまとめました。

保留中シグナルを示すフラグ

シグナル生成によって、シグナルを受け取ったプロセスは、シグナル配送によって処理されるまでの間、シグナル保留状態 (シグナル配送待ち) となります。この状態は TIF_SIGPENDING というフラグで示され、スレッドフラグ (flags) に記録されます。

f:id:Kernel_OGSun:20200524175242j:plain

シグナル保留用のキュー (特定プロセス用、スレッドグループ全体用)

受信したシグナルは、sigqueue 構造体によって管理され、関連する幾つかの情報 (※1) を持ちます。それらは保留用のキューに入れられます (sigpending の list につなげられます)。その際、シグナルの送信宛に応じて 2 種類 (特定プロセス用、スレッドグループ全体用) の内のどちらかのキューが使用されます。

また、保留されたシグナルは signal に記録されます。この値は /proc/<PID>/status から確認できます (前述の「シグナルの状態確認」と後述の「おまけ」参照)。

  • 特定プロセス用

    f:id:Kernel_OGSun:20200524175310j:plain

  • スレッドグループ全体用

    f:id:Kernel_OGSun:20200524175320j:plain

(※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 1
SI_ASYNCNL -60 getaddrinfo_a() での名前検索完了によるシグナル送信

なお、上記はシグナル共通の値です。特定のシグナルによっては、次のように別の値が入ることもあります [^2sigaction]。

シグナル名 si_code に入る値
SIGBUG BUS_*
SIGCHLD CLD_*
SIGFPE FPE_*
SIGILL ILL_*
SIGPOLL/SIGIO POLL_*
SIGSEGV SEGV_*
SIGTRAP TRAP_*
SIGSYS SYS_SECCOMP
シグナルブロック情報

sigprocmask() などによってブロックされたシグナル番号は、ブロック情報 (blocked, readl_blocked8) に記録されます。この値は /proc/<PID>/status から確認できます (前述の「シグナルの状態確認」 と後述の「おまけ」参照)。

f:id:Kernel_OGSun:20200524175409j:plain

シグナルアクション関連の情報

sigaction() などでシグナルアクションの設定を変更すると、シグナルハンドラディスクリプタ (sighand_struct) に幾つかの情報 (※) が格納されます。また、シグナルハンドラディスクリプタの action[x] はシグナル番号 (_NSIG) 個の配列になっているため (action[シグナル番号-1])、シグナル毎に設定されます。

f:id:Kernel_OGSun:20200524175424j:plain

(※) 幾つかの情報とは、次の sigaction 構造体のメンバを指します。

メンバ 意味
sa_flags シグナルの使い方を示す SA_* フラグ[^2sigaction]
sa_handler SIG_IGN、SIG_DFL、シグナルハンドラへのポインタなど、シグナルアクションの種類
sa_mask ハンドラ実行時にブロックする (受信を禁止する) シグナル

2. 実装

シグナルを送信してからシグナルアクションが処理される (kill -TERM 1234 を実行した際の処理) までの流れを見ていきます。

以降、「シグナル生成」と「シグナル配送」で分けてそれぞれ見ていきます (「...」 の部分は省略箇所です)

シグナル生成

この段階での主な仕事は、送信先プロセスのデータ構造に「シグナルを送信した (シグナル配送待ちがある)」ということを送信先プロセスのデータ構造に記録し、通知することです (標準動作といったシグナルアクションはシグナル配送で処理されます)。

以降で行うコードリーディングの要約として、シグナル生成における処理の全体像を図示します。なお、関数の呼び出しが多いため、コードリーディングでは、この中でも特に重要な関数 (コア関数) である __send_signal() から読み進めていきます。

f:id:Kernel_OGSun:20200524175441j:plain

__send_signal()

この関数は、送信されたシグナルを配送するかどうかを決め (prepare_signal())、シグナルを保留用キューに入れます (__sigqueue_alloc(), list_add_tail()) その際、保留シグナルの番号を記録します (sigaddset())。また、配送する場合は、プロセスディスクリプタのスレッドフラグに TIF_SIGPENDING を立て、配送可能なプロセスを起床します (complete_signal())。

詳細は以降をご参照ください。

kernel/signal.c (L1065)

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 を立て、起床します (通知します)。

詳細は以降をご参照ください。

kernel/signal.c (L984)

 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 (プロセスの状態やフラグ有無は見ないで強制する)
  • シグナルがブロックされてない (SIGKILL, SIGSTOP はブロック不可) AND

    • プロセスが終了処理中でない (p->flags に PF_ EXITING が無い) AND 
      • プロセスの状態 (p->state) が一時停止中 (TASK_STOPPED) か、デバッガなどにより停止中 (TASK_TRACED) でない AND
        • プロセスが CPU 上で実行中 (カレントプロセス) OR プロセスのスレッドフラグ (p->thread_info->flags) に TIF_SIGPENDING が無い
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) がない時

(※) sig_fatal() は次の 2 パターンで 真 を返します。

  • シグナルが標準シグナル AND

    • 標準動作が無視 (Ign) / 停止 (Stop) のシグナルでない AND
      • シグナルアクションが SIG_DFL
  • シグナルがリアルタイムシグナル 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 配下) のコードを見ていきます。

f:id:Kernel_OGSun:20200614161842j:plain

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()

この関数は、シグナル保留用キューからシグナルを取り出し、シグナルに応じて標準動作の実行を行います。

詳細は以降をご参照ください。

kernel/signal.c (L2521)

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, &current->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->kaka のアドレスを入れます。これは 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 = &current->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() でも出ましたが9、ここにもシステムコールの再実行に関する処理があります。ただし、カーネルがシステムコールを再実行するケースは次の 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 を入れています。

fs/proc/array.c#L266 (L266)

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 をブロックするように設定しても実際は設定されない)。

kernel/signal.c (L3004)

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 }

■ 参考文献


  1. 詳細は man 5 core をご参照ください。

  2. 詳細は man 7 pipe ご参照ください。

  3. 詳細は man 7 signal をご参照ください。

  4. 詳細は man 2 seccomp ご参照ください。

  5. 詳細は man 2 setrlimit ご参照ください。

  6. man 2 execve カーネルソースなどを調べましたが、この原文以上の情報が見当たらず、意味がよく理解できなかったため、原文のまま記載します。

  7. 詳細は man 1 bash をご参照ください。

  8. 詳細は man 2 sigaction をご参照ください。

  9. 詳細は man 2 signal をご参照ください。

  10. シグナル無視の場合も解除はできますが、シグナル無視の状態で受信したシグナルは保留されないため、解除された後も処理されません。無視を解除した上で再度シグナルを受信する必要があります。

  11. 詳細は man 2 sigprocmask をご参照ください。

  12. タイトルに記載のとおり、カーネルバージョンは調査時点での最新である v5.5 を使用します。

  13. real_blocked は、rt_sigtimedwait(), rt_sigtimedwait_time32(), rt_sigtimedwait_time64() システムコール使用時に設定されるようです。

  14. do_signal() でのシステムコール再実行は、handle_signal() を実行しない場合に通るパス (get_signal() が 偽 の時) なので、ここでのシステムコール再実行処理とは重複しません。