はじめに
Linux の勉強をしていると、kill
コマンドや SIGKILL
, SIGTERM
など、「シグナル」という言葉をよく目にします。
この記事では、そもそも「シグナルとは何なのか?」「kill は何をしているのか?」といったごく基本的なところを、手を動かしながら理解することを目指しました。
シグナルとは
あるプロセスに対して、外部から通知を送るための仕組みです。
たとえば「そろそろ終わってほしい」とか、「今は止まって」など、プロセスに対して“お願い” や “命令” のような形で、何らかの意図を伝える手段です。
シグナルを送るには?
代表的なのは kill
コマンドです。
名前からすると「プロセスを殺す」コマンドのような印象を受けますが、実際には「あるシグナルを、指定したプロセスに送る」だけのツールです。
通知の内容によってはプロセスが終了したり、止まったり、無視したり…
反応の仕方はシグナルの種類とプロセスの設定次第です。
kill コマンドの送り先と通知の種類
シグナルには大きく分けて、2 種類あります:
-
プロセスの自主性に委ねるタイプ(例:
SIGTERM
,SIGINT
)→ 通知を受け取ったプロセスが「終了します」「無視します」などの判断をします
-
カーネルが即座に強制執行するタイプ(例:
SIGKILL
,SIGSTOP
)→ プロセスの設定に関わらず、即終了 or 即停止。プロセスは拒否できない。
つまり kill
は、「通知を送る」ための道具ですが、送り出すシグナルの内容によって、プロセスの動きは大きく変わります。
よく使う代表的なシグナル
kill の引数には、シグナル名、番号名のどちらでも指定できます。
例)kill -SIGTERM [PID]
と kill -15 [PID]
は同じ意味
シグナル名 | 番号 | 意味 | 拒否できる? | 説明 |
---|---|---|---|---|
SIGTERM |
15 | 終了要求 | ✅ できる |
kill コマンドのデフォルト |
SIGINT |
2 | 割り込み(Ctrl+C) | ✅ できる | 対話型中断 |
SIGKILL |
9 | 強制終了 | ❌ 不可 | 即終了。無視もハンドラも無効 |
SIGSTOP |
19 | 一時停止 | ❌ 不可 | 即停止。無視もハンドラも無効 |
SIGTSTP |
20 | 一時停止 (Ctrl+Z) | ✅ できる | ユーザーが明示的に一時停止を指示できる |
SIGCONT |
18 | 一時停止から再開 | ❌ 不可 | 停止状態を解除 |
SIGUSR1 |
10 | ユーザー定義通知 | ✅ できる | プログラムで独自の用途に使える |
運用的な考え方
SIGTERM を送って様子を見て、それでも止まらなければ SIGKILL。
SIGTERM
は「そろそろ終了してくれますか?」というお願いなので、
- アプリはこれを受け取って
cleanup()
を走らせたり - ファイルを閉じたり
- 状態を保存したり
といったきれいな終了処理ができるチャンスを持っています。
一方、SIGKILL は、即ブチ切りするため、ログを残せない、一時ファイルの削除ができない、リソース解放されない、などといった危険があります。使うのは最終手段が良さそう。
trap を使って違いを見てみる
「じゃあ実際、プロセスはどんなふうにシグナルに反応するの?」を試してみました。
trap は、シェルがシグナルを受け取った際の動作を上書きするコマンドです。
※ シグナルを受け取ったときの動作は、プログラム自身が signal()
や sigaction()
を使って設定するほうが通常です。
※ 同様にコマンドでシグナル動作を変える例として nohup
があり、これは SIGHUP
を無視する設定でプログラムを起動します。
trap 'コマンド' [シグナル番号|シグナル名]
の書式で動作を指定できます。
動きを観察するために、こんなスクリプトを作成して、実行してみます。
#!/bin/bash
trap '' TERM # SIGTERMを受け取っても何もしないように設定
echo "PID: $$"
sleep 1000
$ chmod +x [trap-test.sh](http://trap-test.sh/) # 実行権限付与
$ ./trap-test.sh # スクリプト実行
(「PID: 12345」と出たら、その PID をメモ)
このスクリプトを実行しているターミナルとは別のターミナルを開いて、以下のコマンドを入力
kill -SIGTERM 12345
← プロセスは止まらない
kill -SIGKILL 12345
← プロセスが止まる
シグナルの処理タイミング
シグナルは非同期に届き、どのタイミングで処理されるかはプログラム次第です。
一方で、SIGKILL
や SIGSTOP
など拒否できないシグナルは、即時に強制執行されます。
Ctrl + C もシグナルを送っている
よく使う身近なシグナル、Ctrl + C
もシグナルを送っています。
このキーを押すと、ターミナルが実行中のプロセスに SIGINT
(割り込み)シグナルを送ります。
$ sleep 1000
^C
たとえば上のように sleep
を実行中に Ctrl + C
を押すと終了します。
これはプロセスが SIGINT
を受け取って終了しているからです。
ただし、Ctrl + C
は kill [PID]
コマンドのショートカットではありません。
TTY は、ターミナルアプリを介してユーザーが Ctrl + C を押したことを検知すると、フォアグラウンドのプロセスグループに SIGINT
を送る仕組みを持っています。
kill
は PID を指定して「任意のプロセスに明示的にシグナルを送る道具」、
Ctrl + C
は「端末が、ユーザー操作でプロセスグループにSIGINT
シグナルを送る仕組み」です。
どちらも SIGINT
を送る手段ですが、送信元と送信対象の範囲が異なります。
補足: TTY とは?
もともとは、キーボード + プリンタのような物理端末(テレタイプ端末)に由来する用語です。現代のLinuxでは、ターミナルとの接続を表すデバイスファイル /dev/tty1
や /dev/pts/0
などが「TTY」と呼ばれます。本質的には、プロセスが接続されているシリアルラインやその仮想的な代替(PTY)を表すファイルを指します。
ターミナルアプリは、この TTY に接続して入出力をやり取りする窓口です。
なぜ Ctrl + C はプロセスグループに向けてシグナルを送る?
これは「パイプで複数のプロセスが連携して動いているケース」を想定しているからです。
たとえば、次のようなコマンドを実行したとき
$ grep foo file.txt | sort | head
grep
, sort
, head
は 3 つのプロセスとして同時に動いていて、シェルはこれらを「ひとまとまりのジョブ」として扱います。
このとき、それぞれに個別に kill
するのは手間です。
そこで シェルが自動で「プロセスグループ」を作成し、それを TTY に教えておきます。
すると Ctrl + C
を押したとき、TTY はそのプロセスグループ全体にまとめて SIGINT
を送ってくれる、という仕組みです。
おわりに
この記事は「シグナルって何?」と自分で悩んだ結果のまとめです。
まだまだ理解しきれていない部分もありますので、もし誤りや補足があればぜひ教えてください。
同じように「シグナルって何?」と悩んでいる方の参考になれば嬉しいです。
参考サイト
https://man7.org/linux/man-pages/man2/setpgid.2.html
https://man7.org/linux/man-pages/man3/tcsetpgrp.3.html