LoginSignup
162
143

More than 5 years have passed since last update.

Ctrl+CとCtrl+Zやkillなどの挙動の違いを確認する

Posted at

既に色々な記事で書かれていたのですが、自分で理解するためにメモ

参考

結論

  • 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シグナル。プロセスの一時停止(フォアグラウンドプロセスをバックグラウンドに移す場合によく使う)

となってます。
ということで二つは 全く意味が違う ということになります。
以下より、実際にスクリプトを使って挙動の確認をします

確認してみた(フォアグラウンドに対する実行)

簡単なスクリプトで確認してみます。

test.rb
#!/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プロセスグループを理解する

signal-test.rb
#!/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シグナルが送信されたため、同じプロセスグループのプロセス郡は終了となりました。

スクリプトで検証することで内容が確認できました。

162
143
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
162
143