既に色々な記事で書かれていたのですが、自分で理解するためにメモ
参考
結論
- Ctrl+CはSIGINTシグナルという割り込みを示すシグナルを送信し、Ctrl+Zは一時停止を示すSIGTSTPシグナルを送信する
- Ctrl+Cを実行するとフォアグラウンドのプロセスは基本的に終了する(特別にSIGINTシグナルのイベントを受け取るようにしていなければ)
- Ctrl+Zを実行するとフォアグラウンドのプロセスは一時停止状態になる
- 一時停止なので、再開ができる。再開するときにフォアグラウンドでの再開、バックグラウンドの再開ができる
- フォアグラウンドのプロセスに対してCtrl+Cを実行する場合とバックグラウンドのプロセスにkillコマンドでSIGINTシグナルを送信する場合では挙動が違う
- Ctrl+Cによってシグナルを送信する場合、フォアグラウンドで実行しているプロセスのプロセスグループ郡に対してシグナルを送信する(子プロセスがいれば子プロセスにもシグナルを送信する)
- 対してkillコマンドでSIGINTシグナルを送信する場合、指定されたプロセスのみにシグナルを送信する(親プロセスを対象とした場合でも子プロセスにシグナルは送信しない)
シグナルとは
実行中のプロセスに様々なイベントを通知するために利用できるものです。以下の記事によく使うシグナルの表がありました。
Ctrl+Cとkill -SIGINTの違いからLinuxプロセスグループを理解する
上記を見ると
- Ctrl+C->SIGINTシグナル。割り込みの発生
- Ctrl+Z->SIGTSTPシグナル。プロセスの一時停止(フォアグラウンドプロセスをバックグラウンドに移す場合によく使う)
となってます。
ということで二つは 全く意味が違う ということになります。
以下より、実際にスクリプトを使って挙動の確認をします
確認してみた(フォアグラウンドに対する実行)
簡単なスクリプトで確認してみます。
#!/usr/bin/ruby
puts "process id is #{Process.pid}"
while true
File.write('now.txt', Time.now.strftime("%Y-%m-%d %H:%M:%S"))
sleep 1
end
無限ループして1秒ごとにnow.txtの時間表示が更新しています。
Ctrl+Cをしてみる
最初はCtrl+Cを実行してみましょう。
# 現在時刻
$date
2016年 3月 31日 木曜日 16:48:08 JST
# 実行。一定時間経過後、Ctrl+Cでsleepが中断(Interrupt)されたことが表示される
$ruby test.rb
process id is 3266
^Ctest.rb:6:in `sleep': Interrupt
from test.rb:6:in `<main>'
# いつまで実行されていたか確認
$cat now.txt
2016-03-31 16:48:17
# 現在時刻
$date
2016年 3月 31日 木曜日 16:48:34 JST
# プロセスが存在しない(grepした自分自身のみの表示)
$ps aux |grep ruby
pi 3218 0.0 0.1 5724 1852 pts/0 S+ 16:33 0:00 grep --color=auto ruby
# jobにも登録されていない
$jobs
Ctrl+Cの実行によってフォアグラウンドで実行されているtest.rbの実行プロセスにSIGINTシグナルが送信され、プロセスが終了しています。
Ctrl+Zの確認
次にCtrl+Zを実行して確認してみます。
# 現在時刻
$date
2016年 3月 31日 木曜日 16:59:34 JST
# 実行。一定時間経過後、Ctrl+Zで一時停止
$ruby test.rb
process id is 3282
^Z
[1]+ 停止 ruby test.rb
# 現在時刻
$date
2016年 3月 31日 木曜日 16:59:47 JST
# いつまで実行されていたか確認
$cat now.txt
2016-03-31 16:59:42
# プロセスが存在
$ps aux |grep ruby
pi 3282 0.1 0.6 11604 6252 pts/0 Tl 16:59 0:00 ruby test.rb
pi 3293 0.0 0.2 5724 1968 pts/0 S+ 17:01 0:00 grep --color=auto ruby
# 一時中断したジョブとして存在
$jobs
jobs
[1]+ 停止 ruby test.rb
Ctrl+Zの実行によってSIGTSTPシグナルが送信され、プロセスが一時停止の状態となりました。ただし、一時停止なのでプロセス自体はまだ存在しています。
この状態からfg
コマンドによって停止中のプロセスをフォアグラウンドで実行させることができるのでやってみます。
# jobのidを確認
$jobs
[1]+ 停止 ruby test.rb
# fgコマンドでフォアグラウンドで再開。引数にはjobsコマンドで表示される左の数字を指定
$fg 1
ruby test.rb
# 一定時間経過後、再度Ctrl+Zで一時停止
[1]+ 停止 ruby test.rb
# 確認.フォアグラウンドで再開されているので最初に実行した時より時間が進んでいる
$cat now.txt
2016-03-31 17:16:05
再開できることが確認できました。
また、再開なので再度プロセスIDが表示されるわけではなく、途中の処理から開始されているのも確認できます。
次にフォアグラウンドでなく、bg
コマンドを使ってバックグラウンドで実行してみます。
# 確認
$jobs
[1]+ 停止 ruby test.rb
# bgコマンドでバックグラウンドで再開. &を付与してバックグラウンドで実行
$bg 1
[1]+ ruby test.rb &
# バックグラウンドで実行されているので次々と更新される
$cat now.txt
2016-03-31 17:22:06
$cat now.txt
2016-03-31 17:22:11
# 停止中でなく、実行中と表示される
$jobs
[1]+ 実行中 ruby test.rb &
# プロセスの状態は存在
$ps aux |grep ruby
pi 3282 0.0 0.7 13884 7540 pts/0 Sl 16:59 0:00 ruby test.rb
pi 3388 0.0 0.2 5724 1916 pts/0 S+ 17:25 0:00 grep --color=auto ruby
上記より、例えば誤ってバックグラウンドで実行させたいのにフォアグラウンドで実行してしまった場合には、Ctrl+Zによる中断、bgコマンドによってバックグラウンドでの再開をすることができることが分かりました。(Ctrl+Cで終了させると最初からで再開ではない)
バックグラウンドで実行されているプロセスを終了させる場合にはkill
コマンドを使います。
kill [process id
を指定することでSIGTERMシグナルを対象プロセスに送信して終了するように伝えます。
# プロセスIDを確認
$ps aux |grep ruby
pi 3282 0.0 0.9 16844 9176 pts/0 Sl 16:59 0:00 ruby test.rb
pi 3394 0.0 0.1 5724 1852 pts/0 S+ 17:30 0:00 grep --color=auto ruby
# SIGTERMシグナルをプロセスID3282に送る
$kill 3282
# 終了している
ps aux |grep ruby
pi 3396 0.0 0.2 5724 1916 pts/0 S+ 17:31 0:00 grep --color=auto ruby
[1]+ Terminated ruby test.rb
# 更新が止まっていることを確認
$date
2016年 3月 31日 木曜日 17:32:02 JST
$cat now.txt
2016-03-31 17:31:32
Ctrl+Cとkillコマンドによるシグナル送信の違い
Ctrl+C(SIGINT)とCtrl+Z(SIGTSTP)は対象のプロセスに対してシグナルを送信します。ということはkillコマンドによってプロセスに対してシグナルを送ることと同義な気がしますが、特定の場合に挙動が違います。
結論を先に書くと
- フォアグラウンドのプロセスに対してCtrl+Cでシグナルを送る場合には 対象のプロセスのプロセスグループに属する全てのプロセスにシグナルを送る
- killコマンドによってプロセスを指定してシグナルを送る場合には 対象のプロセスのみにシグナルを送信する
という違いがあります。
具体的には以下のように子プロセスを持つ親プロセスに対して実行された場合に異なる挙動となります。
- Ctrl+C->親プロセス及び子プロセス(孫などもあれば同じく)全てにSIGINTシグナルが送信される
- kill [親プロセスID]->親プロセスは終了する。子プロセスは残り、init直下のプロセスとなる
以下より実際に確認を行ってみます。
バックグラウンドの親プロセスにSIGINTシグナルを送る
プログラムは以下で記載しているものと同じものを利用させて頂きました。
Ctrl+Cとkill -SIGINTの違いからLinuxプロセスグループを理解する
#!/usr/bin/ruby
fork {
puts "child process id is #{Process.pid}"
sleep
}
puts "parent process id is #{Process.pid}"
sleep
実行して確認してみます。
# バックグラウンドで実行
$ruby signal-test.rb &
[1] 23010
parent process id is 23010
child process id is 23012
# -aオプションで実行コマンド、-pオプションでプロセスIDを表示(一部省略)
$pstree -ap
|-tmux,22900
| |-bash,22901
| | `-ruby,23010 signal-test.rb
| | |-ruby,23012 signal-test.rb
| | | `-{ruby},23014
| | `-{ruby},23013
あれ、4つプロセスがある?
と思ったのですがman pstree
を見ると{}はスレッドということでした。
psコマンドでも一応確認します。
# 通常psコマンドではスレッドは表示されない
$ps aux |egrep "ruby|PID"
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ec2-user 23010 0.0 0.8 135296 8496 pts/1 Sl 05:46 0:00 ruby signal-test.rb
ec2-user 23012 0.0 0.6 135296 6444 pts/1 Sl 05:46 0:00 ruby signal-test.rb
ec2-user 23137 0.0 0.2 110460 2112 pts/1 S+ 06:10 0:00 grep -E --color=auto ruby|PID
# -Lオプションを追加してスレッドも表示
$ps aux -L |egrep "ruby|PID"
USER PID LWP %CPU NLWP %MEM VSZ RSS TTY STAT START TIME COMMAND
ec2-user 23010 23010 0.0 2 0.8 135296 8496 pts/1 Sl 05:46 0:00 ruby signal-test.rb
ec2-user 23010 23013 0.0 2 0.8 135296 8496 pts/1 Sl 05:46 0:00 ruby signal-test.rb
ec2-user 23012 23012 0.0 2 0.6 135296 6444 pts/1 Sl 05:46 0:00 ruby signal-test.rb
ec2-user 23012 23014 0.0 2 0.6 135296 6444 pts/1 Sl 05:46 0:00 ruby signal-test.rb
ec2-user 23188 23188 0.0 1 0.0 452 4 pts/1 R+ 06:14 0:00 grep -E --color=auto ruby|PID
なるほど。
スレッドは子プロセスではないのでスレッドのプロセスIDは親のプロセスIDとなっています。
つまりpstreeコマンドで{}というスレッドの表示の場合、LWP(スレッドID)になるということですね。
この状態で親プロセスに対してSIGINTシグナルを送ってみます
# 親プロセスにSIGINTシグナルを送る
$kill -SIGINT 23010
signal-test.rb:9:in `sleep': Interrupt
from signal-test.rb:9:in `<main>'
[1]+ Interrupt ruby signal-test.rb
# pstreeで確認(一部省略)
$pstree -ap
init,1
|-ruby,23012 signal-test.rb
| `-{ruby},23014
親プロセス(23010)は表示されなくなりましたが、子プロセスは表示されたままです。そして子プロセスがどうなったかというとプロセスID1のinitプロセスの直下に移動となっています。これを リペアレンディング といい、Linuxのカーネルがやっているようです。
フォアグラウンドのプロセスにCtrl+CでSIGINTシグナルを送る
今度はフォアグラウンドで実行してCtrl+Cによってシグナルを送ります。
フォアグラウンドで実行中のプロセスの確認は別端末(ssh)でログインしたもので確認します。
# フォアグラウンドで実行
$ruby signal-test.rb
parent process id is 23231
child process id is 23233
# 別の端末でプロセスを確認(一部省略)
$pstree -ap
|-tmux,22900
| |-bash,22901
| | `-ruby,23231 signal-test.rb
| | |-ruby,23233 signal-test.rb
| | | `-{ruby},23235
| | `-{ruby},23234
# Ctrl+Cでシグナルを送る
^Csignal-test.rb:9:in `sleep': Interrupt
from signal-test.rb:9:in `<main>'
signal-test.rb:5:in `sleep': Interrupt
from signal-test.rb:5:in `block in <main>'
from signal-test.rb:3:in `fork'
from signal-test.rb:3:in `<main>'
# 再確認.rubyに関するプロセスはない
$pstree -ap
こちらは子プロセスに対してもSIGINTシグナルが送信されたため、同じプロセスグループのプロセス郡は終了となりました。
スクリプトで検証することで内容が確認できました。