Edited at

bashとCtrlキーで送るシグナル


TL;DR


  • シェル(例えばbash)上でCtrl-Cなどを実行すると、プロセスに対してシグナルが送られる

  • プロセスは、受け取ったシグナルに対して応答するだけ


    • 多くのものは、そのまま終了する



  • シグナルは、ハンドリングすることができる



    • SIGKILL除く



  • シェル(というか端末)上で、どんな操作を行うとシグナルを送ったりすることができるのかは、sttyコマンドで確認できる


    • 必ずしもシグナルが送られるわけではない(Ctrl-Dなど)



ということについて、つらつらと書いてみようかなと。

Ctrl-Cを使うと実行中のプロセスを停止できたりしますが、あれはシグナルを送ってるんですよ、という話。

Ctrl-Zだと、サスペンドになりますね。

Ctrl-Dだと、bashが終了しますね。あれはなんなんでしょう?


環境

今回の環境は、こちら。

$ lsb_release -a

No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 18.04.3 LTS
Release: 18.04
Codename: bionic

$ uname -a
Linux xxxxx 4.15.0-60-generic #67-Ubuntu SMP Thu Aug 22 16:55:30 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

Ubuntu Linux 18.04 LTS。


シグナルの一覧と、端末で使える制御コマンドの確認

シグナル自体は、killコマンドで確認しましょう。

$ 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

現在、各キーでどんなシグナルを送ったりすることができるかは、stty -aで確認できます。

$ stty -a

speed 38400 baud; rows 61; columns 220; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc

例えば、intr^Cとなっていますが、これがCtrl-CでありSIGINTなわけですね。

各項目の意味は、--helpで確認できます。

$ stty --help

シグナルを送るのは、このあたりですね。

$ stty --help | grep signal | grep send

intr CHAR CHAR will send an interrupt signal
quit CHAR CHAR will send a quit signal
susp CHAR CHAR will send a terminal stop signal
[-]hup send a hangup signal when the last process closes the tty


シグナルをトラップする

試しに、こんなスクリプトを書いてみます。

handle_signal.sh

#!/bin/bash

while true; do
echo 'sleeping...'
sleep 1
done

実行権限付与。

$ chmod a+x handle_signal.sh

実行。

$ ./handle_signal.sh 

sleeping...
sleeping...

Ctrl-Cで停止。

$ ./handle_signal.sh 

sleeping...
sleeping...
sleeping...
sleeping...
^C

ここまでは、ふつうだと思います。

では、ここでtrapを追加してみましょう。SIGINTをトラップしてみます。

#!/bin/bash

trap 'echo "trap SIGINT signal"' SIGINT

while true; do
echo 'sleeping...'
sleep 1
done

SIGINTシグナルを受け取ったら、echoを実行するようにしてみました。

trapへの引数は、以下のとおりです。

trap 'コマンド' [シグナル名 または シグナルの番号]

今回は、シグナル名で指定しています。

すると、Ctrl-Cで停止しなくなります。

$ ./handle_signal.sh 

sleeping...
sleeping...
^Ctrap SIGINT signal
sleeping...
sleeping...
^Ctrap SIGINT signal
sleeping...
sleeping...

これを停止したかったら、別のターミナルからkillすることになります。

$ kill [PID]

こうなってしまうのは、Ctrl-Cで送信するSIGINTシグナルをハンドリングするようにしたからですね。

ちなみに、SIGTERMをつけてしまうと、killでも停止できなくなります。

trap 'echo "trap SIGTERM signal"' SIGTERM

$ kill [PID]

sleeping...
trap SIGTERM signal

これを停止するには、SIGKILLシグナルを送ることになります。

$ kill -KILL [PID]

SIGKILLシグナルは、trapでハンドリングできません。強制終了シグナルだからです。


Ctrl-Dは?

ところで、Ctrl-Dを入力するとbashが終了しますね?

stty -aの結果と

intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;

--helpを見比べると、どうもシグナルを送るわけではなさそうです。

$ stty --help | grep signal | grep send

intr CHAR CHAR will send an interrupt signal
quit CHAR CHAR will send a quit signal
susp CHAR CHAR will send a terminal stop signal
[-]hup send a hangup signal when the last process closes the tty

これの意味は?

eof = ^D;

「入力終了」を表す文字を送ります、と。

   eof CHAR      CHAR will send an end of file (terminate the input)

これでbashが終了するようになっています。

Ctrl-Sを入力するとbashが停止したように見えたり、Ctrl-Qで再開できるように見えたりするのも、このあたりの情報から読み解けるので、興味があれば見てみるとよいでしょう。