hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

シグナルについて



1 はじめに

シグナルに関して様々な実験を実施してみます。

他にも、以下の記事を投稿しました。
シグナルの受信処理について - hana_shinのLinux技術ブログ

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.1 (Lime Lynx)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-162.6.1.el9_1.x86_64

3 シグナル一覧を表示する方法

killコマンドを実行すると、シグナルの一覧を表示することができます。通常シグナルは1~32までの範囲で、リアルタイムシグナルは33~64までの範囲にあります。

[root@server ~]# 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

4 シグナルを送信する方法

killコマンドを実行すると、プロセスにシグナルを送信することができます。ここでは、sleepプロセスにSIGKILLシグナルを送信してみます。まず、sleepコマンドを実行します。sleepコマンドを実行すると、sleepプロセスが起動され、その後600秒スリープします。

[root@server ~]# sleep 600

psコマンドを実行して、sleepプロセスのPIDを確認します。sleepプロセスのPIDは19876であることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C sleep
    PID TTY          TIME CMD
  19867 pts/2    00:00:00 sleep

4.1 シグナル名を指定する方法

シグナル名を指定してkillコマンドを実行します。

[root@server ~]# kill -SIGKILL 19867

sleepプロセスがSIGKILLシグナルを受信すると、強制終了することがわかります。

[root@server ~]# sleep 600
強制終了

4.2 シグナル番号を指定する方法

シグナル番号を指定してkillコマンドを実行します。

[root@server ~]# kill -9 1188

sleepプロセスがSIGKILLシグナルを受信すると、強制終了することがわかります。

[root@server ~]# sleep 600
強制終了

5 シグナルの状態を表示する方法

psコマンドにsオプションを指定すると、シグナルの各種状態(PENDING、BLOCKED、IGNORED、CAUGHT)を表示することができます。ここでは、これらの各種状態をシグナル情報と呼ぶことにします。シグナル情報は64ビットのデータです。通常シグナルには32ビットが割り当てられており、リアルタイムシグナルにも32ビットが割り当てられています。

[root@server ~]# ps s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0     672 0000000000000000 0000000000000000 0000000000000006 0000000000000000 Ss+  tty1       0:00 /sbin/agetty -o -p
    0    1124 0000000000000000 0000000000010000 0000000000384004 000000004b813efb Ss   pts/0      0:00 -bash
    0    1195 0000000000000000 0000000000000000 0000000000384004 000000004b813efb Ss+  pts/1      0:00 -bash
    0   19660 0000000000000000 0000000000010000 0000000000384004 000000004b813efb Ss   pts/2      0:00 -bash
    0   19908 0000000000000000 0000000000000000 0000000000000000 0000000000000000 S+   pts/2      0:00 sleep 600
    0   19915 0000000000000000 0000000000000000 0000000000000000 0000000073d1fef9 R+   pts/0      0:00 ps s

シグナル情報の意味は以下のとおりです。

シグナル情報 意味
PENDING プロセスが受信したがまだ処理していないシグナルを示します
BLOCKED プロセスが特定のシグナルを受信しないように一時的にシグナルの処理をブロックするシグナルを示します。ブロックを解除するとシグナルを処理します
IGNORED プロセスが特定のシグナルを受信しても無視するシグナルを示します
CAUGHT シグナルハンドラを登録しているシグナルを示します

PIDを指定すると、特定プロセスのシグナル情報を表示することができます。ここでは、systemd(PID=1)のシグナル情報を表示してみます。

[root@server ~]# ps s -p 1
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0       1 0000000000000000 7fe3c0fe28014a03 0000000000001000 00000001000004ec Ss   ?          0:05 /usr/lib/systemd/s

次のようにフォーマットを明に指定しても、プロセスのシグナル情報を表示することができます。

[root@server ~]# ps -p 1 -o comm,pid,sig,sigmask,sigignore,sigcatch
COMMAND             PID          PENDING          BLOCKED          IGNORED           CAUGHT
systemd               1 0000000000000000 7fe3c0fe28014a03 0000000000001000 00000001000004ec

-Cオプションでプロセス名を指定すると、指定したプロセスのシグナル情報を表示することができます。chronydプロセスのシグナル情報を表示してみます。

[root@server ~]#  ps -C chronyd -o comm,pid,sig,sigmask,sigignore,sigcatch
COMMAND             PID          PENDING          BLOCKED          IGNORED           CAUGHT
chronyd             656 0000000000000000 0000000000000000 0000000000001000 0000000100004007

記事末尾のサンプルプログラム(signal.py)を使用してIGNOREDのシグナル名を確認すると、SIGPIPEが無視されていることがわかります。

[root@server ~]# ./signal.py
Please enter a hexadecimal number:0000000000001000
Active signals:
SIGPIPE(13)

次に、chronydプロセスがSIGPIPEシグナルを無視するかどうかを確認するため、chronydプロセスにSIGPIPEシグナルを送信してみます。通常、SIGPIPEを受信するとプロセスは終了しますが、chronydプロセスはSIGPIPEシグナルを無視する設定になっているため、SIGPIPEシグナルを受信してもchronydプロセスは終了しません。

[root@server ~]# kill -SIGPIPE 656

chronydプロセスの状態を確認します。chronydプロセスのPIDは656のままで、chronydプロセスが終了していないことがわかります。

[root@server ~]# ps -C chronyd -o comm,pid,sig,sigmask,sigignore,sigcatch
COMMAND             PID          PENDING          BLOCKED          IGNORED           CAUGHT
chronyd             656 0000000000000000 0000000000000000 0000000000001000 0000000100004007

6 CAUGHTの確認方法

6.1テストプログラムの作成

CAUGHTを確認するためのテストプログラムを作成します。SIGHUP(1)、SIGINT(2)、SIGRTMAX-1(63)、SIGRTMAX(64)の各シグナルについて、シグナルハンドラを登録し、それらシグナルを受信した際にはシグナル番号を表示します。シグナルハンドラでは、fprintf関数やprintf関数ではなく、シグナルセーフなwriteシステムコールを使ってメッセージを出力します。また、main関数ではfprintfの出力先にstderrを指定しています。これは、メッセージ出力をバッファリングせず、即座に端末に出力できるようにするためです。

[root@server ~]# cat signal.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

void sig_int(int sig) {
    char msg[100];
    snprintf(msg, sizeof(msg), "Signal number:%d\n", sig);
    write(STDERR_FILENO, msg, strlen(msg));
}

int main() {
    struct sigaction sa[4];
    int signals[] = {1, 2, 63, 64};

    for (int i = 0; i < 4; ++i) {
        sa[i].sa_handler = sig_int;
        sigemptyset(&sa[i].sa_mask);
        sa[i].sa_flags = 0;
        sigaction(signals[i], &sa[i], NULL);
    }

    fprintf(stderr, "PID=%d\n", getpid());
    while(1) {
        sleep(600);
    }
}

テストプログラムをコンパイルします。

[root@server ~]# gcc -Wall -o signal signal.c

6.2 確認結果

テストプログラムを実行します。

[root@server ~]# ./signal
PID=1421

psコマンドを実行してシグナル情報を確認すると、CAUGHTがc000000000000003であることがわかります。

[root@server ~]# ps -C signal s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0    1421 0000000000000000 0000000000000000 0000000000000000 c000000000000003 S+   pts/1      0:00 ./signal

signal.pyを実行すると、CAUGHTのc000000000000003は、テストプログラムで定義した4つのシグナルを補足することを示していることがわかります。

[root@server ~]# ./signal.py
Please enter a hexadecimal number:c000000000000003
Active signals:
SIGHUP(1), SIGINT(2), SIGRTMAX-1(63), SIGRTMAX(64)

7 PENDINGの確認方法

6章と同じテストプログラムを実行します。signalプロセスのPIDが1207であることがわかります。

[root@server ~]# ./signal
PID=1207

Ctrl+zキーを押下して、signalプロセスを停止状態にします。なお、SIGSTOPシグナルをsignalプロセスに送信しても同じ状態になります。

[root@server ~]# ./signal
PID=1207
^Z
[1]+  停止                  ./signal

psコマンドを使用してsignalプロセスの状態を確認すると、停止状態(STATがT)であることがわかります。

[root@server ~]# ps -C signal s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0    1207 0000000000000000 0000000000000000 0000000000000000 c000000000000003 S+   pts/1      0:00 ./signal

signalプロセスにSIGHUP(1)シグナルを送信します。

[root@server ~]# kill -1 1207

psコマンドを実行してシグナル情報を確認すると、PENDINGが0000000000000001になったことがわかります。

[root@server ~]# ps -C signal s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0    1207 0000000000000001 0000000000000000 0000000000000000 c000000000000003 T    pts/1      0:00 ./signal

signal.pyを実行すると、SIGHUPシグナルが保留されていることがわかります。これは、シグナルを受信するプロセスが停止状態になっているため、SIGHUPシグナルを受信しても処理できずに保留しているからです。

[root@server ~]# ./signal.py
Please enter a hexadecimal number:0000000000000001
Active signals:
SIGHUP(1)

次に、signalプロセスにSIGINT(2)、SIGRTMAX-1(63)、SIGRTMAX(64)を送信します。

[root@server ~]# kill -2 1207
[root@server ~]# kill -63 1207
[root@server ~]# kill -64 1207

psコマンドを実行してシグナル情報を確認すると、PENDINGがc000000000000003になっていることがわかります。

[root@server ~]# ps -C signal s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0    1207 c000000000000003 0000000000000000 0000000000000000 c000000000000003 T    pts/1      0:00 ./signal

signal.pyを使ってPENDINGのシグナルを確認すると、SIGHUP(1)、SIGINT(2)、SIGRTMAX-1(63)、SIGRTMAX(64)が保留中であることがわかります。

[root@server ~]# ./signal.py
Please enter a hexadecimal number:c000000000000003
Active signals:
SIGHUP(1), SIGINT(2), SIGRTMAX-1(63), SIGRTMAX(64)

fgコマンドを実行して、signalプロセスを停止状態からスリープ状態に戻します。

[root@server ~]# fg
./signal
Signal number:64
Signal number:63
Signal number:2
Signal number:1

psコマンドを実行してシグナル情報を確認します。signalプロセスが起床して、保留中のシグナルを処理したので、PENDINGが0になったことがわかります。

[root@server ~]# ps -C signal s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0    1207 0000000000000000 0000000000000000 0000000000000000 c000000000000003 S+   pts/1      0:00 ./signal

8 IGNOREDの確認方法

8.1テストプログラムの作成

IGNOREDを確認するためのテストプログラムを作成します。実行開始から30秒間は、SIGHUP(1)、SIGINT(2)、SIGRTMAX-1(63)、SIGRTMAX(64)を受信しても無視します。30秒経過すると、無視していたシグナルを実行します。

[root@server ~]# cat signal1.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

void sig_int(int sig) {
    char msg[100];
    snprintf(msg, sizeof(msg), "Signal number:%d\n", sig);
    write(STDERR_FILENO, msg, strlen(msg));
}

int main() {
    struct sigaction sa[4];
    int signals[] = {1, 2, 63, 64};
    int block_time = 30;

    for (int i = 0; i < 4; ++i) {
        sa[i].sa_handler = SIG_IGN;
        sigemptyset(&sa[i].sa_mask);
        sa[i].sa_flags = 0;
        sigaction(signals[i], &sa[i], NULL);
    }

    fprintf(stderr, "PID=%d\n", getpid());
    sleep(block_time);
    for (int i = 0; i < 4; ++i) {
        sa[i].sa_handler = sig_int;
        sigaction(signals[i], &sa[i], NULL);
    }

    fprintf(stderr, "Signals unblocked.\n");
    while(1) {
        sleep(600);
    }
    return 0;
}

テストプログラムをコンパイルします。

[root@server ~]# gcc -Wall -o signal1 signal1.c

8.2 確認結果

テストプログラムを実行します。

[root@server ~]# ./signal1
PID=1318

プロセスの状態を確認すると、IGNOREDがc000000000000003になっていることがわかります。

[root@server ~]# ps -C signal1 s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0    1318 0000000000000000 0000000000000000 c000000000000003 0000000000000000 S+   pts/1      0:00 ./signal1

signalプロセスにSIGHUP(1), SIGINT(2), SIGRTMAX-1(63), SIGRTMAX(64)を送信します。

[root@server ~]# kill -1 1318
[root@server ~]# kill -2 1318
[root@server ~]# kill -63 1318
[root@server ~]# kill -64 1318

シグナルを受信してもシグナル番号が表示されないことから、シグナルが無視されていることがわかります。

[root@server ~]# ./signal1
PID=1318

30秒経過したあと、プロセスの状態を確認するとIGNOREDが0000000000000000になっていることがわかります。

[root@server ~]# ps -C signal1 s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0    1318 0000000000000000 0000000000000000 0000000000000000 c000000000000003 S+   pts/1      0:00 ./signal1

signalプロセスにSIGHUP(1), SIGINT(2), SIGRTMAX-1(63), SIGRTMAX(64)を送信します。

[root@server ~]# kill -1 1318
[root@server ~]# kill -2 1318
[root@server ~]# kill -63 1318
[root@server ~]# kill -64 1318

シグナルを受信してシグナルハンドラが実行されたため、シグナル番号が表示されていることがわかります。

[root@server ~]# ./signal1
PID=1318
Signals unblocked.
Signal number:1
Signal number:2
Signal number:63
Signal number:64

9 BLOCKEDの確認方法

9.1テストプログラムの作成

BLOCKEDを確認するためのテストプログラムを作成します。テストプログラムを実行すると30秒間シグナルの受信処理をブロックします。30秒経過するとブロックを解除してシグナルの受信処理を実行します。

[root@server ~]# cat signal2.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>

void sig_int(int sig) {
    char msg[100];
    snprintf(msg, sizeof(msg), "Signal number:%d\n", sig);
    write(STDERR_FILENO, msg, strlen(msg));
}

int main() {
    struct sigaction sa[4];
    int signals[] = {1, 2, 63, 64};
    int block_time = 30;
    sigset_t set;

    for (int i = 0; i < 4; ++i) {
        sa[i].sa_handler = sig_int;
        sigemptyset(&sa[i].sa_mask);
        sa[i].sa_flags = 0;
        sigaction(signals[i], &sa[i], NULL);
    }

    fprintf(stderr, "PID=%d\n", getpid());

    sigemptyset(&set);
    for (int i = 0; i < 4; ++i) {
        sigaddset(&set, signals[i]);
    }
    sigprocmask(SIG_BLOCK, &set, NULL);

    fprintf(stderr, "Signals blocked for %d seconds.\n", block_time);
    sleep(block_time);

    sigprocmask(SIG_UNBLOCK, &set, NULL);
    fprintf(stderr, "Signals unblocked.\n");

    while (1) {
        sleep(600);
    }
    return 0;
}

テストプログラムをコンパイルします。

[root@server ~]# gcc -Wall -o signal2 signal2.c

9.2 確認結果

テストプログラムを実行します。

[root@server ~]# ./signal2
PID=1383

プロセスの状態を確認すると、BLOCKEDがc000000000000003になっており、SIGHUP(1)、SIGINT(2)、SIGRTMAX-1(63)、SIGRTMAX(64)がブロックされれいることを示しています

[root@server ~]# ps -C signal2 s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0    1383 0000000000000000 c000000000000003 0000000000000000 c000000000000003 S+   pts/0      0:00 ./signal2

signalプロセスにSIGHUP(1), SIGINT(2), SIGRTMAX-1(63), SIGRTMAX(64)を送信します。

[root@server ~]# kill -1 1383
[root@server ~]# kill -2 1383
[root@server ~]# kill -63 1383
[root@server ~]# kill -64 1383

プロセスの状態を確認すると、PENDINGがc000000000000003になっており、SIGHUP(1)、SIGINT(2)、SIGRTMAX-1(63)、SIGRTMAX(64)が保留中であることを示しています。これは、シグナルの受信処理がブロックされていてシグナルが保留されているからです。

[root@server ~]# ps -C signal2 s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0    1383 c000000000000003 c000000000000003 0000000000000000 c000000000000003 S+   pts/0      0:00 ./signal2

テストプログラムを実行して30秒経過するとブロックしているシグナルのシグナルハンドラが実行されます。シグナルの処理はリアルタイムシグナルが通常シグナルの処理より優先されます。また、シグナル番号の大きいシグナルから処理されていることがわかります。

[root@server ~]# ./signal2
PID=1383
Signals blocked for 30 seconds.
Signal number:64
Signal number:63
Signal number:2
Signal number:1
Signals unblocked.

プロセスの状態を確認すると、BLOCKEDが0000000000000000 になっていることがわかります。これは、30秒経過してブロックしていたシグナルを解除したからです。

[root@server ~]# ps -C signal2 s
  UID     PID          PENDING          BLOCKED          IGNORED           CAUGHT STAT TTY        TIME COMMAND
    0    1383 0000000000000000 0000000000000000 0000000000000000 c000000000000003 S+   pts/0      0:00 ./signal2

X シグナル名とシグナル番号を表示するプログラム

以下は、シグナル情報の16進数をシグナル名とシグナル番号で表示するプログラムです。

[root@server ~]# cat signal.py
#!/usr/bin/python3

def binary_to_signals(hex_input):
    signals = [
        "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
        "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE",
        "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2",
        "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT",
        "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP",
        "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU",
        "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
        "SIGIO", "SIGPWR", "SIGSYS", "SIGUNUSED", "SIGUNUSED",
        "SIGRTMIN", "SIGRTMIN+1", "SIGRTMIN+2", "SIGRTMIN+3",
        "SIGRTMIN+4", "SIGRTMIN+5", "SIGRTMIN+6", "SIGRTMIN+7",
        "SIGRTMIN+8", "SIGRTMIN+9", "SIGRTMIN+10", "SIGRTMIN+11",
        "SIGRTMIN+12", "SIGRTMIN+13", "SIGRTMIN+14", "SIGRTMIN+15",
        "SIGRTMAX-14", "SIGRTMAX-13", "SIGRTMAX-12", "SIGRTMAX-11",
        "SIGRTMAX-10", "SIGRTMAX-9", "SIGRTMAX-8", "SIGRTMAX-7",
        "SIGRTMAX-6", "SIGRTMAX-5", "SIGRTMAX-4", "SIGRTMAX-3",
        "SIGRTMAX-2", "SIGRTMAX-1", "SIGRTMAX",
    ]

    signals_activated = []
    binary_str = bin(int(hex_input, 16))[2:]
    binary_str = binary_str.zfill(64)

    for i, bit in enumerate(reversed(binary_str)):
        if bit == "1":
            if i < len(signals):
                signal_name = signals[i]
                signal_number = i + 1
                signals_activated.append(f"{signal_name}({signal_number})")

    return signals_activated

if __name__ == "__main__":
    hex_input = input("Please enter a hexadecimal number:")

    if not all(ch in "0123456789abcdefABCDEF" for ch in hex_input):
        print("Input error: Please enter a hexadecimal number.")
    else:
        activated_signals = binary_to_signals(hex_input)
        if not activated_signals:
            print("There are no active signals:")
        else:
            print("Active signals:")
            for i in range(0, len(activated_signals), 5):
                signals_line = ", ".join(activated_signals[i:i+5])
                print(signals_line)

実行権を付与します。

[root@server ~]# chmod 744 signal.py

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ