- サーバー
Linuxプロセス確認と停止|ps・top・kill逆引きコマンド一覧
Linuxでプロセスを確認するには ps aux か top、止めるには kill PID(効かなければ kill -9 PID)を使います。 名前で探すなら pgrep -a 名前、ポートを使っているプロセスなら ss -tlnp で、目的に合ったコマンドを選ぶと一度で答えにたどり着けます。
この記事では、Ubuntu 24.04.4 LTS(procps-ng 4.0.4)で実際に実行した結果を載せながら、目的別の逆引き表、ps の列と STAT 記号の意味、top の画面の読み方、シグナルの使い分け、kill・pkill・killall の違いを解説します。TERM を無視するプロセスを作って KILL で止める例、ゾンビプロセスの扱い、pkill -f が自分のシェルまで一致してしまう例など、試してみてわかったつまずきどころも紹介します。
プロセス確認と停止の逆引き早見表
| やりたいこと | コマンド |
|---|---|
| 全プロセスを一覧 | ps aux / ps -ef |
| CPU使用率が高い順 | ps aux --sort=-%cpu | head |
| メモリ使用量が多い順 | ps aux --sort=-rss | head |
| 名前でPIDを探す | pgrep -a nginx |
| コマンドライン全体で探す | pgrep -af "app.py --port" |
| 名前が完全一致するものだけ | pgrep -x nginx |
| 親子関係を見る | pstree -p PID / ps -ef --forest |
| 子プロセスを探す | pgrep -P 親PID |
| ポートを使っているプロセス | ss -tlnp | grep :8080 / lsof -i :8080 |
| ゾンビを探す | ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/' |
| 起動時刻・経過時間 | ps -o pid,lstart,etime -p PID |
| スレッド数 | ps -o pid,nlwp,cmd -p PID / ps -L -p PID |
| 開いているファイル | lsof -p PID / ls -l /proc/PID/fd |
| リアルタイムで監視 | top / top -p PID |
| 1回だけ top の結果を出力 | top -b -n1 | head -20 |
| 通常の停止(TERM) | kill PID |
| 強制停止(KILL) | kill -9 PID |
| 名前で停止 | pkill -x nginx / killall nginx |
| 一時停止 / 再開 | kill -STOP PID / kill -CONT PID |
| 設定の再読み込み | kill -HUP PID(対応しているプログラムのみ) |
| 生きているか確認 | kill -0 PID && echo alive |
| 優先度を下げる | renice -n 10 -p PID |
| ログアウト後も動かす | nohup コマンド > app.log 2>&1 & |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Linuxプロセス確認と停止|ps・top・kill逆引きコマンド一覧」
検証した環境とサンプルプロセス
| 項目 | 内容 |
|---|---|
| OS | Ubuntu 24.04.4 LTS(コンテナ、カーネル 6.18) |
| procps-ng(ps / top / pgrep / pkill) | 4.0.4 |
| CPU / メモリ | 2コア / 約8GB |
検証用に、次の4つのプロセスを起動しました。
# CPUを使い続ける(argv[0]を t99-burn に変更)
(exec -a t99-burn python3 burn.py) > /dev/null 2>&1 &
# ポート6991で待ち受けるWebサーバー
(exec -a t99-web python3 -m http.server 6991 --bind 127.0.0.1) > web.log 2>&1 &
# 子を作ってすぐ終了させ、回収しない(ゾンビを作る)
python3 zombie.py &
# スレッド4本+150MBを確保して待つ
python3 threads.py &
zombie.py は次の内容です。
import os, time
pid = os.fork()
if pid == 0:
os._exit(0) # 子はすぐ終了
print(f"parent={os.getpid()} child={pid}", flush=True)
time.sleep(600) # 親は wait() しない
psの列の意味とSTAT記号一覧
主要な列を指定して表示する
ps -o で必要な列だけを並べると、調べたいことが一目でわかります。
ps -o pid,ppid,user,stat,%cpu,%mem,vsz,rss,tty,etime,time,nlwp,cmd -p 6575,6576,6577,6578,6580
PID PPID USER STAT %CPU %MEM VSZ RSS TT ELAPSED TIME NLWP CMD
6575 1 root R 96.2 0.0 18356 7900 ? 00:23 00:00:22 1 t99-burn burn.py
6576 1 root S 0.2 0.2 30824 18856 ? 00:23 00:00:00 1 t99-web -m http.server 6991 --bind 127.0.0.1
6577 1 root S 0.0 0.0 18352 8140 ? 00:23 00:00:00 1 python3 zombie.py
6578 1 root Sl 0.4 1.9 467116 162440 ? 00:23 00:00:00 5 python3 threads.py
6580 6577 root Z 0.0 0.0 0 0 ? 00:23 00:00:00 1 [python3] <defunct>
| 列 | 意味 | 読み方のポイント |
|---|---|---|
| PID | プロセスID | kill の対象 |
| PPID | 親プロセスのID | 1 なら親が先に終わったか、デーモンとして起動 |
| STAT | 状態(下の表) | R・D・Z に注目 |
| %CPU | CPU使用率 | 起動からの平均。今の値は top で見る |
| %MEM | 物理メモリに占める割合 | RSS ÷ 総メモリ |
| VSZ | 仮想メモリサイズ(KiB) | 予約しただけの領域も含むので大きく出やすい |
| RSS | 実際に物理メモリにある量(KiB) | メモリ使用量はまずRSSを見る |
| TT | 端末 | ? は端末なし(デーモン・バックグラウンド) |
| ELAPSED | 起動からの経過時間 | etimes で秒数 |
| TIME | 消費したCPU時間の累計 | 6575 は23秒で22秒=ほぼ1コア使い切り |
| NLWP | スレッド数 | 6578 は本体+4本で5 |
| CMD | コマンドライン | comm ならプログラム名のみ |
threads.py は 150MB を確保したので RSS が約162MB(162440 KiB)になっています。VSZ は 467MB と大きいですが、これはスレッド用スタックなど予約領域を含むためで、メモリ不足の判断には RSS を使います。
STATの記号一覧
| 記号 | 状態 | 説明 |
|---|---|---|
| R | Running | 実行中または実行待ち |
| S | Sleeping | 割り込み可能な待機(入力待ち・タイマー待ちなど)。大半はこれ |
| D | Disk sleep | 割り込み不可の待機(ディスクやNFSのI/O待ち)。KILLも待たされる |
| T | Stopped | STOP シグナルや Ctrl+Z で一時停止中 |
| t | Tracing stop | デバッガで停止中 |
| Z | Zombie | 終了したが親に回収されていない |
| I | Idle | カーネルのアイドルスレッド |
STAT の2文字目以降に付く記号です。
| 記号 | 意味 |
|---|---|
< |
優先度が高い(nice が負) |
N |
優先度が低い(nice が正) |
s |
セッションリーダー |
l |
マルチスレッド |
+ |
フォアグラウンドのプロセスグループ |
実際に renice -n 10 -p 6575 で優先度を下げると、ps -o pid,ni,cmd の NI 列が 0 から 10 に変わり、STAT に N(優先度が低い)が付きます。
ps aux と ps -ef の違い
| 形式 | 由来 | 特徴 |
|---|---|---|
ps aux |
BSD形式(ハイフンなし) | %CPU・%MEM・RSS・STAT が出る。負荷の確認向き |
ps -ef |
UNIX(System V)形式 | PPID・開始時刻が出る。親子関係の確認向き |
ps -eo 列,列 |
UNIX形式+列指定 | スクリプトで使うならこれ |
目的別ワンライナーと実行結果
CPU順・メモリ順に並べる
ps -o pid,stat,cmd --sort=-%cpu -p 6575,6576,6577,6578,6580
ps -o pid,rss,cmd --sort=-rss -p 6575,6576,6577,6578,6580
PID STAT CMD
6575 R t99-burn burn.py
6578 Sl python3 threads.py
6576 S t99-web -m http.server 6991 --bind 127.0.0.1
6577 S python3 zombie.py
6580 Z [python3] <defunct>
PID RSS CMD
6578 162440 python3 threads.py
6576 18856 t99-web -m http.server 6991 --bind 127.0.0.1
6577 8140 python3 zombie.py
6575 7900 t99-burn burn.py
6580 0 [python3] <defunct>
全プロセスから上位を見るときは ps aux --sort=-%cpu | head -6 のように -p を外します。--sort の前の - は降順を表します。
名前で探す(pgrep)
pgrep -f t99-web # コマンドライン全体から探してPIDだけ
pgrep -l -f t99-burn # PIDとプロセス名
pgrep -a python3 # PIDとコマンドライン全体
6576
6756
6575 python3
6756 bash
ここで、t99-web を指定したのに 6756 bash も一致しています。 6756 はこのコマンドを実行していたシェル自身で、シェルのコマンドライン文字列に t99-web が含まれていたためです。pgrep は自分自身は除外しますが、呼び出し元のシェルは除外しません。これが pkill -f で問題になる例は後で紹介します。
| オプション | 照合対象 | 用途 |
|---|---|---|
| なし | プロセス名(最大15文字)の部分一致 | ざっくり探す |
-x |
プロセス名の完全一致 | 誤一致を避ける |
-f |
コマンドライン全体 | 引数で区別したいとき |
-u ユーザー |
実行ユーザーで絞る | pgrep -u www-data |
-P PPID |
親PIDで絞る | 子プロセス探し |
-n / -o |
最新 / 最古の1つ | 複数起動しているとき |
親子関係を見る(pstree / ps --forest)
pstree -p 6577
pstree -p 6578
python3(6577)---python3(6580)
python3(6578)-+-{python3}(6581)
|-{python3}(6582)
|-{python3}(6583)
`-{python3}(6584)
{} で囲まれているのはスレッドです。ps で木構造を出すには --forest を使います。
ps -f --forest -p $(pgrep -d, -g プロセスグループID)
UID PID PPID C STIME TTY TIME CMD
root 11151 11150 0 23:40 ? 00:00:00 bash -c sleep 120 & sleep 121 & wait
root 11153 11151 0 23:40 ? 00:00:00 \_ sleep 120
root 11154 11151 0 23:40 ? 00:00:00 \_ sleep 121
全体を見るなら ps -ef --forest | less です。
ポートを使っているプロセスを探す
ss -tlnp | grep 6991
lsof -i :6991
LISTEN 0 5 127.0.0.1:6991 0.0.0.0:* users:(("python3",pid=6576,fd=3))
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python3 6576 root 3u IPv4 14989 0t0 TCP localhost:6991 (LISTEN)
ss -tlnp の -p は root でないと他ユーザーのプロセス名が表示されません。 一般ユーザーなら sudo ss -tlnp とします。ポートの使用中エラー(Address already in use)の詳しい対処は別記事にまとめています。
ゾンビを探す
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /^Z/'
6580 6577 Z [python3] <defunct>
ゾンビは PPID(親)を見るのが大切です。 対処はこの後の「止まらないとき」で説明します。
起動時刻・経過時間・スレッド数
ps -o pid,lstart,etime,etimes -p 6575
ps -o pid,nlwp,cmd -p 6578
ps -L -o pid,lwp,stat,cmd -p 6578
PID STARTED ELAPSED ELAPSED
6575 Thu Oct 1 23:38:41 2026 00:23 23
PID NLWP CMD
6578 5 python3 threads.py
PID LWP STAT CMD
6578 6578 Sl python3 threads.py
6578 6581 Sl python3 threads.py
6578 6582 Sl python3 threads.py
6578 6583 Sl python3 threads.py
6578 6584 Sl python3 threads.py
lstart は起動日時、etime は [[日-]時:]分:秒 形式、etimes は秒数です。「何日も動き続けているプロセス」を探すなら ps -eo pid,etimes,cmd --sort=-etimes | head が便利です。
開いているファイル・/procの情報
ls /proc/6576/fd | wc -l
lsof -p 6576 | head -5
grep -E '^(State|PPid|Threads|VmRSS)' /proc/6578/status
4
State: S (sleeping)
PPid: 1
VmRSS: 162440 kB
Threads: 5
(lsof -p の出力は、cwd・txt・mem などの種類ごとに開いているファイルが1行ずつ並びます。上では省略しています。)
/proc/PID/ には cmdline・environ・cwd・fd/ などがあり、ps に出ない情報も直接読めます。
topの画面の読み方とキー操作
top -b -n1 は「バッチモードで1回だけ出力」で、ログ保存やスクリプトに使えます。-p でプロセスを絞った結果です。
top -b -n1 -p 6575,6576,6578,6580 | head -20
top - 23:39:18 up 10 min, 0 user, load average: 1.06, 1.35, 0.82
Tasks: 4 total, 1 running, 2 sleeping, 0 stopped, 1 zombie
%Cpu(s): 52.8 us, 0.0 sy, 0.0 ni, 25.0 id, 22.2 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 8031.1 total, 3851.7 free, 1390.0 used, 3150.1 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 6641.1 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6575 root 20 0 18356 7900 5016 R 100.0 0.1 0:34.98 python3
6576 root 20 0 30824 18856 9380 S 0.0 0.2 0:00.05 python3
6578 root 20 0 467116 162440 5144 S 0.0 2.0 0:00.11 python3
6580 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 python3
ヘッダー部分
| 行 | 項目 | 読み方 |
|---|---|---|
| 1行目 | load average | 1分・5分・15分の平均。CPUコア数(この環境は2)を継続的に超えると待ちが発生 |
| 2行目 | Tasks | zombie が増え続けていないか |
| 3行目 | us / sy | ユーザー空間 / カーネルの CPU 使用率 |
| id | アイドル(空き) | |
| wa | I/O待ち。高いならディスクを疑う | |
| st | 仮想マシンでホストに取られた時間 | |
| 4行目 | buff/cache | キャッシュ。必要なら解放されるので「使用中」とは別 |
| 5行目 | avail Mem | 実際に使える目安のメモリ |
プロセス部分
| 列 | 意味 |
|---|---|
| PR / NI | 優先度 / nice 値 |
| VIRT / RES / SHR | 仮想 / 物理(ps の RSS 相当)/ 共有メモリ(KiB) |
| S | 状態(ps の STAT と同じ記号) |
| %CPU | 直近の更新間隔での使用率。1コア使い切りで100、2コアなら最大200 |
| TIME+ | CPU時間の累計(1/100秒まで) |
ps では %CPU が 96.2(起動からの平均)、top では 100.0(直近)と表示されている点が違いです。「今重いのはどれか」は top で見ます。 なお top の COMMAND は既定でプログラム名(comm)のため、exec -a で付けた名前ではなく python3 と表示されています。c キーでコマンドライン表示に切り替わります。
よく使うキー操作
| キー | 動作 |
|---|---|
P / M / T |
CPU順 / メモリ順 / CPU時間順 |
c |
コマンドライン全体を表示 |
1 |
CPUコアごとに表示 |
H |
スレッド単位で表示 |
u |
ユーザーで絞り込み |
o |
条件で絞り込み(例:COMMAND=nginx) |
k |
PIDとシグナルを入力して kill |
W |
現在の設定を ~/.config/procps/toprc に保存 |
q |
終了 |
起動オプションでは top -o %MEM(メモリ順)、top -u ユーザー、top -d 5(5秒間隔)がよく使われます。
シグナルの種類と使い分け
kill は「プロセスを終了させるだけのコマンド」ではなく「シグナルを送るコマンド」です。 既定では TERM(15)を送ります。
kill -l で番号と名前の一覧を表示できます。
| シグナル | 番号 | 送り方 | 意味・用途 | プロセス側で無視できるか |
|---|---|---|---|---|
| TERM | 15 | kill PID |
終了の依頼。後片付けしてから終われる | できる |
| INT | 2 | Ctrl+C / kill -INT |
割り込み(端末からの中断) | できる |
| HUP | 1 | kill -HUP |
端末切断。デーモンでは設定の再読み込みに使う慣習 | できる |
| KILL | 9 | kill -9 |
即時終了。後片付けなし | できない |
| STOP | 19 | kill -STOP |
一時停止 | できない |
| TSTP | 20 | Ctrl+Z | 端末からの一時停止 | できる |
| CONT | 18 | kill -CONT / fg / bg |
再開 | ― |
| USR1 / USR2 | 10 / 12 | kill -USR1 |
プログラムごとの独自用途(ログ再オープン等) | できる |
| QUIT | 3 | Ctrl+\ | 終了+コアダンプ | できる |
止めるときは TERM → 数秒待つ → それでも残れば KILL の順にすると、プログラムが一時ファイルの削除やバッファの書き出しを行う時間を確保できます。
TERMを無視するプロセスをKILLで止める
TERM・HUP・USR1 を受け取ってメッセージを出すだけのプログラムを用意しました。
# ignore_term.py
import signal, time, os
signal.signal(signal.SIGTERM, lambda s, f: print(f"[{os.getpid()}] TERMを受け取りましたが無視します", flush=True))
signal.signal(signal.SIGHUP, lambda s, f: print(f"[{os.getpid()}] HUPを受け取りました(設定を再読み込みする想定)", flush=True))
signal.signal(signal.SIGUSR1, lambda s, f: print(f"[{os.getpid()}] USR1を受け取りました(ログを再オープンする想定)", flush=True))
print(f"started pid={os.getpid()}", flush=True)
while True:
time.sleep(1)
python3 -u ignore_term.py > ig.log 2>&1 & IG=$!
kill -HUP $IG; kill -USR1 $IG; kill $IG; sleep 1.5
ps -o pid,stat,cmd -p $IG
kill -STOP $IG; ps -o pid,stat,cmd -p $IG
kill -CONT $IG; ps -o pid,stat,cmd -p $IG
kill -KILL $IG; wait $IG; echo "exit=$?"
cat ig.log
PID STAT CMD
6905 S python3 -u ignore_term.py
PID STAT CMD
6905 T python3 -u ignore_term.py
PID STAT CMD
6905 S python3 -u ignore_term.py
/bin/bash: line 22: 6905 Killed python3 -u ignore_term.py > ig.log 2>&1
exit=137
started pid=6905
[6905] HUPを受け取りました(設定を再読み込みする想定)
[6905] USR1を受け取りました(ログを再オープンする想定)
[6905] TERMを受け取りましたが無視します
TERM を送っても状態は S のまま(動き続けている)で、STOP で T、CONT で S に戻り、KILL で終了しました。 KILL はプロセス側で受け取り方を変えられないため、ログに何も残っていません。
終了コードでシグナルを見分ける
シグナルで終了したプロセスの終了コードは「128+シグナル番号」です。検証では TERM で 143、INT(Ctrl+C)で 130、上の KILL で 137 になりました。コンテナやジョブの終了コードが 137 なら KILL(メモリ不足による強制終了のことも多い)、143 なら TERM と判断できます。
kill・pkill・killallの違い
| コマンド | 指定方法 | 照合ルール | 向いている場面 |
|---|---|---|---|
kill |
PID | ― | 対象を確実に1つに絞る |
pkill |
パターン | プロセス名(15文字まで)の部分一致、-f でコマンドライン全体 |
柔軟に指定したい |
killall |
名前 | プロセス名の完全一致 | 同名のプロセスをまとめて |
sleep をコピーした t99nap と、名前が15文字を超える t99-long-process-name を起動して比べました。
./t99nap 300 & ./t99nap 301 & ./t99-long-process-name 302 &
pgrep -l t99na
killall t99na; echo rc=$?
pgrep -a -f "t99nap 301"
pkill -e -f "t99nap 301"
killall -v t99nap
pgrep t99-long-process-name; echo rc=$?
pgrep -l t99-long-proces
killall -v t99-long-process-name; echo rc=$?
8993 t99nap
8994 t99nap
t99na: no process found
rc=1
8994 ./t99nap 301
t99nap killed (pid 8994)
Killed t99nap(8993) with signal 15
pgrep: pattern that searches for process name longer than 15 characters will result in zero matches
Try `pgrep -f' option to match against the complete command line.
rc=1
8995 t99-long-proces
Killed t99-long-process-name(8995) with signal 15
rc=0
| 結果からわかること | 内容 |
|---|---|
| pgrep / pkill は部分一致 | t99na で t99nap が見つかる |
| killall は完全一致 | t99na では no process found |
-f で引数まで区別できる |
"t99nap 301" だけを停止 |
| プロセス名は15文字で切れる | t99-long-proces と表示され、長い名前の pgrep は0件(警告あり) |
| killall は長い名前も扱える | 実行ファイル名と照合するため一致した |
pkill -f が自分のシェルに一致する例
pkill -f は、実行しているシェルのコマンドラインにパターンが含まれていると、そのシェルにも一致します。 検証中、bash -c '... pkill -e -f "t99nap 301" ...' の形で実行したところ、次のように表示されました。
bash killed (pid 7814)
t99nap killed (pid 7822)
対象の t99nap と一緒に、コマンドを実行していた bash も停止しています。ssh 経由や bash -c で実行するときに起こりやすい挙動です。
| 対策 | 例 |
|---|---|
先に pgrep -af で一致するものを確認 |
pgrep -af "app.py" |
| 名前の完全一致を使う | pkill -x t99nap |
| 正規表現で自分の文字列と一致させない | pkill -f "[a]pp.py --port 8080" |
nohup・&・jobs・fg・bg・disownの使い方
| コマンド・操作 | 動作 |
|---|---|
コマンド & |
バックグラウンドで起動 |
| Ctrl+Z | フォアグラウンドのジョブを一時停止(TSTP) |
jobs -l |
ジョブ番号とPIDを一覧 |
fg %1 |
ジョブ1をフォアグラウンドに戻す |
bg %1 |
停止中のジョブ1をバックグラウンドで再開 |
kill %1 |
ジョブ番号で停止 |
disown %1 |
ジョブ一覧から外す(ログアウト時に HUP を送らない) |
nohup コマンド & |
HUP を無視する設定で起動 |
set -m(ジョブ制御を有効化)したスクリプトで実行した結果です。
$ sleep 200 &
$ sleep 300 &
$ jobs -l
[1]- 9232 Running sleep 200 &
[2]+ 9233 Running sleep 300 &
$ kill -STOP %2 # Ctrl+Z と同じ一時停止
$ jobs -l
[1]- 9232 Running sleep 200 &
[2]+ 9233 Stopped (signal) sleep 300
$ bg %2
[2]+ sleep 300 &
$ jobs -l
[1]- 9232 Running sleep 200 &
[2]+ 9233 Running sleep 300 &
$ disown %1
$ jobs -l
[2]+ 9233 Running sleep 300 &
+ はカレントジョブ(fg だけで戻る対象)、- はその次です。disown したジョブは jobs から消えますが、プロセス自体は動き続けます。
nohup で起動したプロセスは、HUP を無視する設定になります。/proc/PID/status の SigIgn(無視しているシグナルのビット)で確認できます。
SigIgn: 0000000000000001 ← nohup sleep(1ビット目=HUPを無視)
SigIgn: 0000000000000000 ← disown しただけの sleep
| 方法 | ログアウト時の HUP | 出力先 |
|---|---|---|
& のみ |
届く → 終了することが多い | 端末(閉じると書けない) |
& disown |
シェルから送られない | 端末のまま |
nohup ... & |
無視する | 端末なら nohup.out、リダイレクトすればその先 |
setsid コマンド |
別セッションなので届かない | 端末のまま |
| tmux / screen | セッションが残る | 再接続して確認できる |
この検証では標準出力が端末ではなかったため nohup.out は作られませんでした。nohup コマンド > app.log 2>&1 & と出力先を明示する形にすると、どの環境でも同じ動きになります。常駐させるサービスは systemd のユニットにするのがおすすめです。
killしても消えないときの判断フロー
flowchart TD
A[kill しても消えない] --> B{ps の STAT は?}
B -->|Z| C[ゾンビ:親PIDを確認]
C --> C1[親に回収させる / 親を停止する]
B -->|D| D[I/O待ち:KILLは保留される]
D --> D1[ディスク・NFS・ストレージを確認]
B -->|T| E[一時停止中:kill -CONT か fg]
B -->|S/R| F{PIDが変わっていないか?}
F -->|変わった| G[親やsystemdが再起動している]
G --> G1[systemctl stop / 親を先に停止]
F -->|同じ| H[TERMを無視している → 数秒待って kill -9]
ゾンビ(Z)は親を見る
ゾンビはすでに終了しているので、KILL を送っても変化しません。 親が wait() で回収すると消えます。
$ kill -KILL 6580 # ゾンビ本体に送る
PID PPID STAT CMD
6577 1 S python3 zombie.py
6580 6577 Z [python3] <defunct>
$ kill 6577 # 親を止める
PID PPID STAT CMD
6577 1 Z [python3] <defunct>
6580 1 Z [python3] <defunct>
親を止めると、ゾンビの PPID が 1 に変わりました。通常の Linux では PID 1 の systemd がすぐに回収します。この検証環境はコンテナで、PID 1 は systemd ではなく process_api という別のプログラムだったため、数秒後に回収されて一覧から消えました。
Docker などのコンテナで、アプリ自身が PID 1 になっている場合は、子のゾンビが回収されずに残ることがあります。docker run --init や tini のような小さな init を PID 1 にすると回収されるようになります。
D状態はI/Oの完了を待つ
D(割り込み不可の待機)中のプロセスは、KILL を送っても I/O が終わるまで終了しません。 この記事の環境では D 状態を安定して再現できなかったため実行結果はありませんが、確認は次のコマンドで行えます。
ps -eo pid,stat,wchan:32,cmd | awk '$2 ~ /^D/'
cat /proc/PID/stack # root のみ。カーネル内でどこを待っているか
wchan には待っているカーネル関数名が表示されます。NFS サーバーの応答待ちやディスクの不調が多いため、dmesg や iostat で I/O の状況を確認します。
親や監視役が再起動している
PID が変わって同じプロセスが出てくる場合は、親(監視役)が再起動しています。 子を起動し直すループを作って試しました。
(while true; do ./t99nap 100; done) &
$ pstree -p 9748
bash(9748)---t99nap(9750)
$ kill 9750
$ pstree -p 9748
bash(9748)---t99nap(9754)
子(9750)を止めると、すぐに新しい子(9754)が起動しました。親の 9748 を止めると、最後の子 9754 は PPID 1 で残ったため、PID を指定して停止しました。
| 監視役 | 止め方 |
|---|---|
| systemd | systemctl stop サービス名(Restart= が設定されていると kill しても戻る) |
| シェルのループ・スーパーバイザー | 親を先に止め、残った子を止める |
| プロセスグループごと | kill -- -PGID(マイナス付きでグループ全体に送る) |
| コンテナ | docker stop / kubectl delete pod |
よくある質問
Q. kill と kill -9 はどう使い分けますか?
A. まず kill PID(TERM)で終了を依頼し、数秒待っても残っていれば kill -9 PID(KILL)を使います。KILL はプロセスが後片付けできないため、一時ファイルやロックファイルが残ることがあります。
Q. ps の %CPU と top の %CPU が違うのはなぜですか?
A. ps は「起動してからの平均」、top は「直近の更新間隔での値」だからです。今どのプロセスが重いかを知りたいときは top か top -b -n1 を使います。
Q. pkill と killall はどちらを使えばよいですか?
A. 名前を正確に指定するなら killall か pkill -x、引数まで含めて区別したいなら pkill -f です。どちらも実行前に pgrep -a(-f なら pgrep -af)で一致するプロセスを確認すると安心です。
Q. ゾンビプロセスは消さないといけませんか? A. ゾンビ自体はメモリをほとんど使いません。数が増え続ける場合は、親プロセスが子を回収していないので、親の再起動や修正で対処します。
まとめ
- 確認は
ps aux(負荷)とps -ef(親子)、今重いものはtop - 名前で探すなら
pgrep -a、引数まで見るならpgrep -af、ポートならss -tlnp - メモリは VSZ ではなく RSS を見る、STAT は R・D・Z に注目
- 停止は TERM → 待つ → KILL の順、終了コード 137 は KILL、143 は TERM
- killall は完全一致、pkill は部分一致、
pkill -fは自分のシェルにも一致しうる - ログアウト後も動かすなら
nohup ... > log 2>&1 &、常駐は systemd へ - 消えないときは STAT と PPID を見て、ゾンビ・D状態・再起動を見分ける
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Linuxプロセス確認と停止|ps・top・kill逆引きコマンド一覧」
株式会社RJCでは、Linuxのプロセス管理の知識を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。