1. 概要

Linuxシステムでは、プロセスはSIGINTやSIGKILLなどのさまざまなシグナルを受信できます。 各信号はさまざまな状況で送信され、それぞれの動作が異なります。

この記事では、SIGINT、SIGTERM、SIGQUIT、およびSIGKILLについて説明します。 それらの違いもわかります。

2. シグナル入門

信号は、プロセス間の通信方法です。 プロセスがシグナルを受信すると、プロセスはその実行を中断し、シグナルハンドラーが実行されます。

プログラムの動作は通常、受信した信号の種類によって異なります。 信号を処理した後、プロセスは通常の実行を続行する場合と続行しない場合があります。

Linuxカーネルは信号を送信できます。たとえば、プロセスがゼロで除算しようとすると、SIGFPE信号を受信します。

killプログラムを使用して信号を送信することもできます。 バックグラウンドで簡単なスクリプトを実行して停止しましょう。

$ (sleep 30; echo "Ready!") &
[1] 26929
$ kill -SIGSTOP 26929
[1]+  Stopped                 ( sleep 30; echo "Ready!" )

これで、SIGCONTを使用して再開できます。

$ kill -SIGCONT 26929
Ready!
[1]+  Done                    ( sleep 30; echo "Ready!" )

または、キーの組み合わせを使用して端末で信号を送信することもできます。 たとえば、Ctrl + CはSIGINTを送信し、Ctrl + SはSIGSTOPを送信し、Ctrl+QはSIGCONTを送信します。

各シグナルにはデフォルトのアクションがありますが、プロセスはデフォルトのアクションをオーバーライドして別の方法で処理することも、無視することもできます。 ただし、一部の信号は無視したり、別の方法で処理したりすることができず、デフォルトのアクションが常に実行されます。

trap コマンドを使用して、bashで信号を処理できます。 たとえば、スクリプトに trap date SIGINT を追加すると、SIGINTを受信した日付が出力されます。

3. SIGINT

SIGINTは、Ctrl+Cを押したときに送信される信号です。 デフォルトのアクションは、プロセスを終了することです。 ただし、一部のプログラムはこのアクションをオーバーライドし、異なる方法で処理します。

一般的な例の1つは、bashインタープリターです。 Ctrl + Cを押しても終了せず、代わりに新しい空のプロンプト行を出力します。 もう1つの例は、gdbを使用してプログラムをデバッグする場合です。 Ctrl + CでSIGINTを送信して実行を停止し、gdbのインタープリターに返すことができます。

SIGINTは、ユーザーから送信された中断要求と考えることができます。通常、SIGINTの処理方法は、プロセスと状況によって異なります。

trapコマンドを使用してhandle_sigint.sh を記述し、SIGINTを処理して、現在の日付を出力してみましょう。

#!/bin/bash

trap date SIGINT

read input
echo User input: $input
echo Exiting now

読み取り入力を使用して、ユーザーの操作を待ちます。 それでは、スクリプトを実行して、Ctrl+Cを押しましょう。

$ ./handle_sigint.sh 
^CSat Apr 10 15:32:07 -03 2021

スクリプトが存在しなかったことがわかります。 これで、入力を記述してスクリプトを終了できます。

$ ./handle_sigint.sh 
^CSat Apr 10 15:32:07 -03 2021
live long and prosper
User input: live long and prosper
Exiting now

シグナルを使用してシグナルを終了する場合、このスクリプトでSIGINTを使用することはできません。 代わりに、SIGTERM、SIGQUIT、またはSIGKILLを使用する必要があります。

4. SIGTERMおよびSIGQUIT

SIGTERMおよびSIGQUITシグナルは、プロセスを終了するためのものです。この場合、特にプロセスの終了を要求しています。 SIGTERMは、killコマンドを使用する場合のデフォルトのシグナルです。

両方の信号のデフォルトのアクションは、プロセスを終了することです。 ただし、SIGQUITは、終了する前にコアダンプも生成します。

SIGTERMを送信すると、プロセスは終了する前にクリーンアップルーチンを実行することがあります。

SIGTERMを処理して、終了する前に確認を求めることもできます。 handle_sigterm.sh というスクリプトを作成して、ユーザーがシグナルを2回送信した場合にのみ終了するようにします。

#!/bin/bash

SIGTERM_REQUESTED=0
handle_sigterm() {
    if [ $SIGTERM_REQUESTED -eq 0 ]; then
        echo "Send SIGTERM again to terminate"
        SIGTERM_REQUESTED=1
    else
        echo "SIGTERM received, exiting now"
        exit 0
    fi
}

trap handle_sigterm SIGTERM

TIMEOUT=$(date +%s)
TIMEOUT=$(($TIMEOUT + 60))

echo "This script will exit in 60 seconds"
while [ $(date +%s) -lt $TIMEOUT ]; do
    sleep 1;
done
echo Timeout reached, exiting now

それでは、バックグラウンドで実行してみましょう $ ./handle_sigterm.sh& 。 次に、実行します $キル 2回:

$ ./handle_sigterm.sh &
[1] 6092
$ kill 6092
Send SIGTERM again to terminate
$ kill 6092
SIGTERM received, exiting now
[1]+  Done                    ./handle_sigterm.sh

ご覧のとおり、スクリプトは2番目のSIGTERMを受信した後に終了しました。

5. SIGKILL

プロセスがSIGKILLを受信すると、プロセスは終了します。 これは、無視できず、動作を変更できないため、特別なシグナルです。

このシグナルを使用して、プロセスを強制的に終了します。 プロセスはクリーンアップルーチンを実行できないため、注意が必要です。

SIGKILLを使用する一般的な方法の1つは、最初にSIGTERMを送信することです。 プロセスを終了するための時間を与えます。また、SIGTERMを数回送信する場合もあります。 プロセスが自然に終了しない場合は、SIGKILLを送信してプロセスを終了します。

前の例を書き直して、SIGKILLを処理し、確認を求めてみましょう。

#!/bin/bash

SIGKILL_REQUESTED=0
handle_sigkill() {
    if [ $SIGKILL_REQUESTED -eq 0 ]; then
        echo "Send SIGKILL again to terminate"
        SIGKILL_REQUESTED=1
    else
        echo "Exiting now"
        exit 0
    fi
}

trap handle_sigkill SIGKILL

read input
echo User input: $input

それでは、ターミナルで実行して、SIGKILLを1回だけ送信してみましょう。 $ kill -SIGKILL

$ ./handle_sigkill.sh
Killed
$

信号の再送信を要求せずにすぐに終了することがわかります

6. SIGINTとSIGTERM、SIGQUIT、およびSIGKILLとの関係

信号について理解が深まったので、信号が互いにどのように関連しているかを確認できます。

SIGINT、SIGTERM、SIGQUIT、およびSIGKILLのデフォルトのアクションは、プロセスを終了することです。 ただし、SIGTERM、SIGQUIT、およびSIGKILLはプロセスを終了するシグナルとして定義されていますが、SIGINTはユーザーによって要求された中断として定義されています。

特に、プロセスと状況に応じてSIGINTを送信する(またはCtrl + Cを押す)と、動作が異なる可能性があります。 したがって、プロセスを完了するためにSIGINTだけに依存するべきではありません。

SIGINTはユーザーが送信するシグナルとして意図されているため、通常、プロセスは他のシグナルを使用して相互に通信します。 たとえば、SIGINTが同じ効果を持つ場合でも、親プロセスは通常、子プロセスにSIGTERMを送信して子プロセスを終了します。

SIGQUITの場合、デバッグに役立つコアダンプが生成されます。

これを念頭に置いたので、プロセスを終了するには、SIGKILLの上にSIGTERMを選択する必要があることがわかります。 プロセスが正常に終了する可能性があるため、SIGTERMが推奨される方法です。

プロセスはSIGINT、SIGTERM、およびSIGQUITのデフォルトのアクションをオーバーライドできるため、どちらもプロセスを終了しない場合があります。 また、プロセスがハングしている場合、これらの信号のいずれにも応答しない可能性があります。 その場合、プロセスを終了する最後の手段としてSIGKILLがあります。

7. 結論

この記事では、シグナルと、SIGINT、SIGTERM、SIGQUIT、およびSIGKILLの違いについて学びました。 また、bashで信号を処理する方法を簡単に学びました。

SIGINTは、意味が異なる可能性があるため、プロセスを強制終了しない場合があることを確認しました。 一方、SIGKILLシグナルは常にプロセスを終了します。

また、SIGQUITはデフォルトでコアダンプを生成し、SIGTERMがプロセスを強制終了するための推奨される方法であることも学びました。