1-6. プロセスとジョブ制御 - ps, top, kill, nohup
所要時間: 25-50分(がっつりなら2セッション分) ゴール: 動いているプロセスを観察・操作できる。フリーズしたプロセスを安全に止められる コミット内容: 今日の操作履歴を
~/log/linux_day06.logに保存
この章が終わるとできること
ps auxの出力を見て「PID / 所有者 / CPU / MEM / コマンド」を読めるtop/htopで CPU/メモリ食い をリアルタイム特定できるkill <PID>とkill -9 <PID>の 使い分け ができるCtrl+Z→bg/fg、nohup ... &でジョブ制御が書けるlsof -i :3000で「ポートを掴んでる犯人」を即特定できる
Day 1-5 とのつながり
- Day 4-5 で 「ファイル・テキストを扱う道具」 を覚えた
- 今日からは 「動いているプログラム(プロセス)」 が主役
- Day 5 の
> log 2>&1 &の&の正体(バックグラウンド起動)が今日明らかになる
これができると何が嬉しいか
- 「サーバーが重い」と言われた瞬間に
topで犯人特定 → 1分以内に状況報告できる - 「ポート3000が使用中」を秒で解消できる
- SSH を切っても処理が止まらない
nohupパターンが書ける(Day 11 systemd の前哨戦)
大前提: プロセスを理解しないとサーバーで戦えない
「サーバーが重い」「メモリ食ってる」「APIが応答しない」――現場で頻繁に発生するこれらの症状は、すべて プロセス を見れば原因が分かる。
- アプリが固まった → どのプロセスがCPUを食ってる?
- メモリ不足でデプロイ失敗 → 何が大量にメモリ使ってる?
- ポート3000がbind済みって何? → 何のプロセスがそのポートを掴んでる?
- 本番サーバーで実行したスクリプトが、SSH切ったら止まった → ジョブ制御を知らないからこうなる
プロセス管理は 「サーバーの中で何が起きているかを見て・操る」 ための基礎。今日は ps / top / kill / ジョブ制御 / nohup を学ぶ。
セッション①: プロセスの可視化(25-30分)
0. 録画スタート&作業ディレクトリ
mkdir -p ~/log ~/learn/linux/day06
cd ~/learn/linux/day06
script ~/log/linux_day06.log1. プロセスとは何か
# 自分のシェル自体のプロセスID
# ↓ $$ は「いま動いている自分のシェル自身の PID」を表すシェル特殊変数。
# $PPID は「その親プロセスの PID」を表す環境変数(どちらも `$<名前>` で値を参照する書き方)。
# 特殊変数・環境変数の体系は Linux 2-2 で詳しく扱う。ここでは数値が表示されればOK。
echo $$
# 親プロセスのID
echo $PPIDプロセス = 「起動中のプログラム」
ディスクに保存された「プログラム(実行ファイル)」が OSのメモリ上で動き出すと「プロセス」になる。
1つのプログラムから何個でもプロセスが作れる(chromeを2窓開けば chrome プロセスが2つ)。
プロセスを識別する番号:
- PID (Process ID): プロセス固有の番号(OS起動から付与される連番)
- PPID (Parent Process ID): そのプロセスを起動した「親プロセス」のPID
Linuxでは すべてのプロセスは必ず親を持つ。木構造(プロセスツリー)になっている。 一番上の親は PID 1 = init / systemd(OSが最初に起動するプロセス)。
プロセスの親子関係(重要)
シェルでコマンドを実行すると:
- シェル(zsh)が
fork()で自分のコピーを作る- コピーされた子プロセスが
exec()で目的のプログラムに変身する- 子プロセスが終わるまでシェルは「待つ」
- 子プロセスが終了 → シェルが終了ステータスを受け取る → 次のプロンプトを表示
この仕組みのせいで:
- 子プロセスは親シェルの環境変数を引き継ぐ(だから
export VAR=...が効く)- 親シェルが死ぬと子プロセスは行き場を失う(孤児プロセス、ゾンビプロセス)
&でバックグラウンドにすると親シェルは待たない
2. ps - プロセスを列挙する
ps (process status) は 今この瞬間に動いているプロセスの一覧を表示するコマンド。スナップショット型なので top のような動的更新はせず、「実行した瞬間の状態」を静止画として返す。オプションで表示形式・対象範囲を変えられる。
# 自分が起動したプロセスだけ
ps
# 詳細表示 BSD系オプション(macOSデフォルト)
ps aux
# 詳細表示 System V系オプション
ps -ef
# プロセスツリー(親子関係を視覚化)
ps -ef --forest # Linux
pstree # 別途インストール必要
# 特定の名前を含むプロセスだけ
ps aux | grep nginx
ps auxの各列の意味USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
列 意味 USER 起動したユーザー PID プロセスID %CPU CPU使用率(瞬間値) %MEM メモリ使用率 VSZ 仮想メモリサイズ RSS 実メモリ使用量(KB) TTY 端末(バックグラウンドなら ?)STAT 状態(後述) START 起動時刻 TIME 累計CPU時間 COMMAND 実行コマンド
STAT列の意味(プロセスの状態)
文字 意味 RRunning(実行中、または待機列にいる) SSleeping(割り込み可能なスリープ、応答待ち) DUninterruptible sleep(割り込み不可。多くはディスクI/O待ち) ZZombie(後述、親に回収待ち) TStopped(一時停止中。Ctrl+Z で来る状態) +フォアグラウンド(末尾につく) sセッションリーダー 「D state」のプロセス: ディスクI/Oで詰まっている。kill しても応答しない厄介者。ストレージかネットワークが詰まってる兆候。
ps aux vs ps -ef どう違う?
ざっくり言うと
両方とも「全プロセス表示」だが、出身が違う2つの方言。aux は BSD 系、-ef は System V 系。出てくる情報は似てるが、表示フォーマットが違う。
まず普通のパターン
両方打ってみると、似てるけど違う:
ps aux
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# takato 123 0.5 1.2 ...
ps -ef
# UID PID PPID C STIME TTY TIME CMD
# takato 123 100 0 10:00 ? 00:00:01 /usr/bin/...「結局どっち使えばいいの?」となる。
なぜ2種類あるのか
Unix の歴史で 「BSD 系」 と 「System V 系」 という2つの流派があって、それぞれ ps のオプション体系が違った。Linux はどちらも吸収したので、両方使える状態。
ps の歴史:
BSD (Berkeley) ── ps aux のスタイル
System V (AT&T) ── ps -ef のスタイル
↓
Linux が両対応
違いを図で見る
ps aux:
USER %CPU %MEM が見やすい
→ 「重いプロセスを探す」のに向く
→ macOS 文化圏で多く使われる
ps -ef:
PPID(親プロセスID)が出る
→ 「親子関係を追う」のに向く
→ 商用 Unix / Linux サーバー文化圏で多い
対比表
ps aux | ps -ef | |
|---|---|---|
| 出身 | BSD | System V |
| ハイフン | 無し | 有り(-ef) |
%CPU | 出る | 出ない(C 列で近い情報) |
%MEM | 出る | 出ない |
RSS(実メモリ) | 出る | 出ない |
PPID(親PID) | 出ない | 出る |
| よく見る環境 | macOS、開発機 | Linux サーバー、商用 Unix |
使い分け
| やりたいこと | コマンド |
|---|---|
| メモリ・CPU 使用率を見たい | ps aux |
| 親子関係を追いたい | ps -ef |
| とにかく全プロセス見たい | どっちでもOK |
grep と組み合わせる | どっちでもOK |
イメージ
ps aux= リソース監視向き(重いやつを探す)ps -ef= 系統樹追跡向き(親子関係を追う)
一番覚えやすい説明
ps auxは CPU/メモリ重視(macOS でよく使う)ps -efは 親子関係重視(Linux サーバーでよく使う)- どちらも全プロセスを出すので、迷ったら自分が慣れた方でOK
ps の実務ユースケース
- 特定プロセスを探す:
ps aux | grep nginx- メモリ食ってるプロセス上位:
ps aux --sort=-%mem | head(macOSは--sort非対応、top -o memなどで代替)- 特定ユーザーのプロセスだけ:
ps -u takato- 本番サーバーで「動いてるプロセス全部」を取る:
ps -ef > processes_$(date +%Y%m%d).log
ps aux | grepの罠
ps aux | grep pythonには grepコマンド自身もマッチしてしまう(grep のコマンドラインに “python” が含まれるため)。回避策:
ps aux | grep python | grep -v grep # grep行を除外 ps aux | grep "[p]ython" # 正規表現トリック:[p]ython はマッチするが、grepの引数自体は "[p]ython" なのでマッチしない pgrep -af python # 専用コマンド
3. top / htop - リアルタイム監視
top は プロセス状況をリアルタイムで監視・自動更新するコマンド。CPU使用率・メモリ使用率・ロードアベレージなどシステム全体の状態を秒単位で追跡できる。htop はその高機能版で、バーグラフ表示・マウス操作・カラフルな UI に対応。
# top(標準で入っている)
top
# 主要キー操作
# q : 終了
# k : kill(PID入力 → シグナル番号入力)
# M : メモリ使用率でソート
# P : CPU使用率でソート(デフォルト)
# 1 : CPUコアごとに表示
# r : niceness(優先度)変更
# htop(インストール推奨)
brew install htop
htop
topの本質ps が「静止画」だとすれば、top は「動画」。デフォルトでは2-3秒ごとに表示を更新し、リアルタイムでプロセス状況を表示する。
上部にシステム全体の情報(CPU使用率、メモリ、ロードアベレージ)、下部にプロセス一覧。
「ロードアベレージ」とは: 過去1分、5分、15分の「実行待ち+実行中プロセス数」の平均。CPUコア数を超えると過負荷のシグナル。4コアCPUで
load average: 5.2 5.0 4.8なら全コアが詰まってる。
htop の方がおすすめな理由
top の高機能版。視覚的に分かりやすい:
- CPU/メモリのバーグラフ が上に出る
- マウス操作可 (PIDクリック → 操作)
- F9 で kill、 F5 でツリー表示 などキー操作が直感的
- 横スクロールでコマンド全体が見える
本番Linuxサーバーには標準で入ってないことが多いので、
apt install htopなどで追加する。
top/htop の実務ユースケース
- 「アプリが重い」と言われた瞬間に top を立ち上げる → CPU/メモリの状況を10秒で把握
- メモリリーク調査: 数分単位で同じプロセスのメモリが増え続けていないか
- 特定のプロセスだけ監視:
top -p <PID>(特定PIDだけ)- 負荷テスト中のリソース監視: 別ターミナルで
htopを表示しながら ab / wrk を回す
4. kill - プロセスを止める
kill は 指定したプロセスに「シグナル」を送るコマンド。名前は「殺す」だが実態は信号送信で、シグナル種類を変えれば「終了してね」「強制終了」「設定再読み込み」など別の指示も送れる。フリーズしたプロセスを止める時に頻出。
# 仮の長時間プロセスを起動(30秒スリープ)
sleep 30 &
# [1] 12345 ← jobs番号とPIDが表示される
# PIDで止める(デフォルトはSIGTERM)
kill 12345
# 強制終了(SIGKILL)
kill -9 12345
# または
kill -KILL 12345
# 名前で止める(pkill)
pkill -f "sleep 30"
# プロセスグループ全体を止める(PGIDで)
kill -- -<PGID>kill は「シグナル送信」コマンド
名前は物騒だが、kill の本来の意味は「プロセスにシグナルを送る」。
kill = process killではなくkill = signal send。シグナル = OSがプロセスに送る「通知」のようなもの。シグナルを受け取ったプロセスは、それに応じた動作をする(終了する、設定を再読み込みする、一時停止する等)。
シグナルには番号と名前がある:
主要なシグナル
番号 名前 意味 挙動 1 SIGHUP Hang Up ターミナル切断(設定再読み込みに使う場合も) 2 SIGINT Interrupt Ctrl+C を押した時に送られる 9 SIGKILL Kill 強制終了(プロセスは無視できない、即死) 15 SIGTERM Terminate 通常の終了要求(プロセスが受け取って後始末できる) 17/19 SIGSTOP Stop 一時停止(無視できない) 18 SIGCONT Continue 再開 20 SIGTSTP Terminal Stop Ctrl+Z を押した時に送られる ※番号はLinuxとmacOSで微妙に異なる場合あり
SIGTERM vs SIGKILL の違い
ざっくり言うと
両方とも「プロセスを止める」シグナルだが、プロセスに渡る前の挨拶の有無が違う。
- SIGTERM = 「終わってくれない?」と頼む(プロセスは後始末ができる)
- SIGKILL = 「いま即死」(プロセスは何もできない)
まず普通のパターン
kill 12345 # ← SIGTERM(デフォルト)
kill -9 12345 # ← SIGKILL見た目はオプション1つの違いだけ。でも プロセス側で起きていること が全く違う。
SIGTERM の流れ
あなた: kill 12345
↓
OS: 「SIGTERM 受け取って」とプロセスに通知
↓
プロセス:
├─ DB 接続をクローズ
├─ 書き込み途中のファイルを保存
├─ ログに「シャットダウンします」と書く
└─ そして終了
↓
ターミナルに戻る
プロセスが「後始末」できる。データを失わない。
SIGKILL の流れ
あなた: kill -9 12345
↓
OS: 即座にプロセスを破棄(プロセスには通知すら行かない)
↓
プロセス: 何もできず即死
↓
├─ DB 接続は切れっぱなし
├─ ファイル書き込み途中で停止 → 破損の可能性
└─ ログには何も残らない
バツン、と落ちる。データ破損リスクあり。
イメージ
- SIGTERM = 「お先に失礼します、片付けます」を許す
- SIGKILL = 「いま、消す」(問答無用)
なぜプロセスは SIGTERM を無視できるのか
SIGTERM は シグナルハンドラ で受け止められる。プロセスが「俺はちゃんと後始末してから死にたいから、SIGTERM 来たら shutdown() を呼ぶ」と書いておけば、それが実行される。
SIGTERM → プロセス側で「受け取りました、後始末します」と言える
SIGKILL → OS が直接プロセスを止める。プロセスには通知すら行かない
つまり SIGKILL は OS が殺す、SIGTERM は プロセスが自分で死ぬ。
対比表
| SIGTERM (15) | SIGKILL (9) | |
|---|---|---|
| デフォルト? | はい(kill <PID>) | いいえ(-9 必要) |
| プロセスは無視できる? | できる(ハンドラ次第) | できない(OS が強制) |
| 後始末の機会 | ある | ない |
| データ破損リスク | 低い | 高い |
| 体感 | 「やんわり依頼」 | 「即死」 |
正しい使い方の順序
1. まず kill <PID> ← SIGTERM
2. 数秒待つ ← プロセスに後始末させる
3. それでも残ってたら kill -9 <PID> ← 最終手段
いきなり -9 を打つのは最終手段。本番 DB で -9 を打って書き込み途中のデータが破損する事故は実在する。
一番覚えやすい説明
- SIGTERM = 依頼(後始末させる、データ守れる)
- SIGKILL = 即死(OS が強制終了、データ破損リスク)
- 常に SIGTERM から試す、
-9は粘った末の最終手段
kill の実務ユースケース
- フリーズしたアプリを止める: まず
kill <PID>→ ダメならkill -9 <PID>- nginxに設定を再読み込みさせる:
kill -HUP <nginxのmaster PID>(一部のサーバーはSIGHUPで設定リロード)- デバッグ中のサーバーを止める:
pkill -f "node server.js"- 特定ユーザーのプロセスを全部止める:
pkill -u username(強い操作なので注意)
kill の落とし穴
kill -9が癖になる: 「とりあえず-9」で問題が起きないように見えるが、データ破損やソケットの残留(次回起動時にbindできない)を引き起こす- PIDを取り違える: 本番で関係ないプロセスを kill する事故。
ps aux | grepで確認 → コピペでPID指定が安全- 権限: 自分が所有しないプロセスは kill できない(root か
sudo killが必要)kill 0は「自分のプロセスグループ全部にシグナル送信」という危険操作。打ち間違いに注意
pkill / killall - 名前で kill
pkill -f "node server" # コマンドラインが一致するプロセスを kill pkill -u takato firefox # 特定ユーザーの firefox を kill killall nginx # nginx という名前のプロセス全部を kill(複数あれば全滅)便利だが「意図しないものまで巻き込む」リスクがあるので、本番では
ps + 目視確認 + PID指定が安全。
セッション②: ジョブ制御と「ターミナル切っても動かす」(25-30分)
5. ジョブ制御の基本
ジョブ制御は 「1つのターミナルで複数のコマンドを同時並行に動かす」ためのシェルの機能。フォアグラウンドで動かしているコマンドを Ctrl+Z で一時停止 → bg で裏に回す → fg で表に戻す、という操作で「画面を取られずに別作業をする」が実現できる。& を末尾に付ければ最初からバックグラウンド起動も可能。
実務でハマりがちなシーン:
- 開発サーバー(
npm run dev)を動かしながら別コマンドを叩きたい vimで編集中、シェルコマンドをちょっと叩きたくなった →Ctrl+Zで一時退避 → 用事を済ませてfgで戻る- 複数の
tail -fを切り替えて見たい
「ジョブ番号 %1」と「PID」は別物(ジョブ番号はシェルが管理する通し番号、PID は OS が振る一意番号)なので、混同しないこと。
# 長時間プロセスを起動
sleep 100
# Ctrl+Z で一時停止 → 「Stopped」と表示される
# ジョブ一覧
jobs
# バックグラウンドで再開
bg %1 # %1 はジョブ番号
# フォアグラウンドに戻す
fg %1
# 最初からバックグラウンドで起動
sleep 100 &
# ジョブ番号と PID は別物
# ↓ $! は「いちばん最近 & でバックグラウンド起動したプロセスの PID」が自動で入るシェルの特殊変数。
# 詳しい特殊変数の体系($$, $?, $@ など)は Linux 2-2 で扱う。今は「$! を echo すれば直前の & の PID が見える」と覚えればOK。
echo $! # 直前にバックグラウンド起動したプロセスのPIDfg vs bg vs jobs のジョブ制御
ざっくり言うと
ジョブには 3つの状態 がある:
- フォアグラウンド = ターミナルを占有して動いてる(普通の状態)
- バックグラウンド = 裏で動いてる(
&付き) - 停止中 = 一時停止(
Ctrl+Z直後)
これらを切り替えるのが fg / bg、現状確認が jobs。
まず普通のパターン
sleep 100これを実行すると、ターミナルが奪われる(100秒間プロンプトが返ってこない)。
「いま別作業もしたいんだけど…」となる。ここでジョブ制御の出番。
状態遷移図
Ctrl+Z
[フォアグラウンド] ─────────→ [停止中]
↑ ↓ │
│ fg %1 │ & │ bg %1
│ │ │
│ ↓ ↓
└──── [バックグラウンド] ←──┘
fg %1
3つの状態を Ctrl+Z & fg bg で行ったり来たりできる。
シーンで覚える: 「動いてる sleep を裏に回したい」
sleep 100 # フォアグラウンドで実行 → ターミナル占有
↓ Ctrl+Z
[1]+ Stopped sleep 100 ← 停止中
↓ bg %1
[1]+ sleep 100 & ← バックグラウンドで再開
↓ プロンプト戻る、別作業できるシーンで覚える: 「裏にあるジョブを前に戻したい」
sleep 100 & # 最初からバックグラウンド
↓ jobs
[1]+ Running sleep 100 &
↓ fg %1
sleep 100 ← フォアグラウンドに戻る
↓ Ctrl+C で終わらせるそれぞれの役割
jobs = 現状確認
jobs
# [1]+ Running sleep 100 &
# [2]- Stopped vim file.txt「いまどんなジョブがあるか」を一覧表示。+ は「直近に操作したジョブ」、- は「その前」。
fg = フォアグラウンドに戻す
fg %1 # ジョブ1を前面に
fg # 直近のジョブ(+ 付き)を前面にbg = バックグラウンドで再開
bg %1 # 停止中のジョブ1を裏で再開Ctrl+Z で止めたものを、裏で動かし続けたい時に使う。
ジョブ番号 vs PID
ジョブ番号 (%1) | PID | |
|---|---|---|
| 誰が管理? | シェルが管理 | OSが管理 |
| スコープ | そのシェル内だけ | システム全体で一意 |
| 表記 | %1, %2, … | 12345 のような数値 |
| 他ターミナルから見える? | 見えない | 見える |
ジョブ番号は シェルだけのローカル番号。別ターミナルからは見えない。
対比表
| コマンド | 役割 | 入力例 |
|---|---|---|
jobs | 現状確認 | jobs |
Ctrl+Z | フォアグラウンドを一時停止 | (実行中に) |
& | 最初から裏で起動 | sleep 100 & |
fg %N | バックグラウンド/停止中 → フォアグラウンド | fg %1 |
bg %N | 停止中 → バックグラウンド | bg %1 |
kill %N | ジョブ番号で終了 | kill %1 |
イメージ
- フォアグラウンド = 舞台の上(観客(ターミナル)がそのプロセスを観てる)
- バックグラウンド = 舞台裏(動いてるけど観客は別を観れる)
- 停止中 = ロッカー(後で取り出して舞台のどちらに戻すか選べる)
一番覚えやすい説明
jobs= いま何が動いてる?Ctrl+Z→bg= フォアグラウンドのやつを裏に回すfg %1= 裏や停止中のやつを前に戻す- ジョブ番号
%1は PID とは別物(シェル内だけの通し番号)
ジョブ制御の実務ユースケース
- 開発サーバーを動かしながら別作業:
npm run dev &でバックグラウンド起動 → コーディング再開- 複数の
tail -fを切り替えながら見る: 1個目をCtrl+Z→tail -f another.log→fg %1で戻す- GUIアプリを起動して即ターミナルに戻る:
code . &(VS Code 起動してターミナル即解放)
6. nohup と disown - ターミナル切断後も動かす
nohup と disown は 「ターミナル(SSHセッション)を閉じてもプロセスを動かし続けたい」場面で使うコマンド。SSHで本番サーバーに繋いで長時間バッチを起動した時、普通に ./batch.sh & で動かすとログアウト = SSH切断と同時にシェルが死に、子プロセスにも SIGHUP が伝播してバッチが殺される。nohup は最初から「SIGHUP無視」付きで起動するラッパー、disown は既に動いているジョブを後からシェル管理から外す手段、と役割が違う。
nohup: コマンドを起動する瞬間に「SIGHUP無視」を仕込むラッパー(事前防御)disown: 既にバックグラウンドで動いているジョブを「シェルの子じゃない」扱いに切り替える(事後対応)
デプロイ作業中に回線が切れても処理は完走、というのが本質的なメリット。長時間ジョブを安心して放置するための道具。
# nohup でログイン切断耐性
nohup ./long_task.sh &
# → 自動で nohup.out というファイルに出力が保存される
# 出力先を指定
nohup ./long_task.sh > task.log 2>&1 &
# disown - すでに動いているジョブを「親なしモード」に
sleep 1000 &
disown %1 # %1 ジョブをシェルから切り離す
# ジョブ一覧から消える
jobs # 空& vs nohup vs disown の違い
ざっくり言うと
普通に & で動かしただけだと、ターミナルを閉じたときに一緒に死ぬ可能性がある。
それを防ぐのが nohup と disown。
まず普通のパターン
./batch.sh &これは「裏で動かしてね」という意味。 でも、このプロセスはまだそのシェルにぶら下がっているジョブ。
ターミナル
└─ シェル
└─ ./batch.sh
この状態でSSHが切れると:
ターミナル切断
↓
シェル終了
↓
子プロセスに SIGHUP が飛ぶ
↓
./batch.sh が死ぬことがある
nohup は「最初から死なないように起動」
nohup ./batch.sh &これは「このコマンドはターミナルが閉じても終了しないでね」を最初から付けて起動。
イメージ: nohup = ターミナル切断に強い状態で起動する
実務:
nohup ./batch.sh > batch.log 2>&1 &出力先を明示しないと自動で nohup.out というファイルにリダイレクトされる。
disown は「後から切り離す」
うっかり普通に起動した場合:
./batch.sh &
jobs # [1] Running ./batch.sh &
disown %1 # シェルのジョブ管理から外すイメージ: disown = このジョブ、もうシェルの管理対象じゃないことにする
違い
& のみ | nohup | disown | |
|---|---|---|---|
| いつ使う? | 一時的に裏で動かす | 起動する前 | 起動した後 |
| ターミナル切断耐性 | なし | あり(最初から) | あり(後から付与) |
| 出力 | 端末に流れる | nohup.out に保存 | そのまま(出力先は変えない) |
| 性格 | 普通 | 予防 | 応急処置 |
一番覚えやすい説明
&は 裏で動かす(だけ。切断には弱い)nohupは 予防(起動前にお守りを付ける)disownは 応急処置(起動済みを後から切り離す)
nohup / disown の落とし穴
- 出力先: nohup は
nohup.outに書く。ディレクトリ書き込み権限が無いと失敗する- 本当に動いてるか不安: バックグラウンド化すると挙動が見えない。
tail -f nohup.outで監視するクセを- メモリ・ディスクを食い続ける: 監視がないと暴走に気付かない。次のセクション(top / htop)で監視
- より現代的な選択肢: 本番サーバーで永続化したいなら systemd サービス化 が王道(プロセス監視・自動再起動も得られる)。nohup は「とりあえず動かしたい」一時しのぎ
7. バックグラウンドプロセスとデーモンの違い
バックグラウンドプロセス vs デーモン
どちらも「画面に出ずに裏で動く」プロセスだが、性質が違う:
バックグラウンドプロセス デーモン 起動方法 command &でシェルが起動OSが起動時に自動起動(systemd等) 親プロセス 起動したシェル init (PID 1) ターミナルに紐付く? 紐付く(nohup/disown で切れる) 紐付かない 終了タイミング 親シェル終了で道連れ(SIGHUP) 明示的に止めるまで動き続ける 用途 開発中の手作業 サーバープロセス(nginx, mysql, redis等) デーモン化 = プロセスが自分自身を「親と切り離して」「stdin/stdout/stderrを切って」「特定の作業ディレクトリに移って」起動する処理。古くはアプリ側で書いていたが、現代では systemd に任せる。
8. ポートを掴んでいるプロセスを特定する
lsof / netstat / ss は 「このポート、誰が使ってる?」を特定するコマンド群。npm run dev で Error: listen EADDRINUSE: address already in use :::3000 を見た時、犯人プロセスを見つけて kill するための定番セット。
lsof -i :<port>: macOSでも Linuxでも動く万能解。「list open files」の意味で、Unix では「ソケットもファイル」なのでネットワークもこれで見えるnetstat: 昔ながらの定番。最近の Linux では非推奨でssへの移行が進んでいるss: netstat の現代版(socket statistics)。表示が速くてオプションも豊富
特に Web 開発では「同じポートで前回のプロセスが残ってる → bind できない」が日常茶飯事。lsof -i :3000 → PID 取得 → kill <PID> の3ステップを反射で打てるようになると詰まらない。
# Linux
sudo lsof -i :3000
sudo netstat -tlnp | grep 3000
sudo ss -tlnp | grep 3000
# macOS
lsof -i :3000
# プロセスのカレントディレクトリを見る(PIDから)
ls -l /proc/<PID>/cwd # Linux のみ
lsof= list open filesプロセスが開いている「ファイル」を列挙する。Unixでは ソケット(ネットワーク接続)もファイル扱い なので、
lsof -i :3000で「ポート3000を掴んでるプロセス」が分かる。頻出シーン:
- 「ポート3000 が already in use」エラー →
lsof -i :3000→ 該当PIDをkill- 「DBに接続できない」 →
lsof -i :5432で PostgreSQL がポートを開いているか確認- 「このファイル誰が掴んでる?」 →
lsof <ファイル名>
9. 練習課題
# 1. バックグラウンドで長時間プロセスを起動
# ↓ $! は「直前に & で起動したバックグラウンドプロセスの PID」を表すシェル特殊変数(前節で説明)。
# `JOB_PID=$!` は「その PID を JOB_PID という名前の変数に保存する」シェルの変数代入。
# 変数代入と参照($JOB_PID)の文法は Linux 2-2 で詳しくやる。
# 今は「`変数名=値` で代入、`$変数名` で参照」とイメージできればOK。
sleep 60 &
JOB_PID=$!
echo "起動したPID: $JOB_PID"
# 2. jobs で確認
jobs
# 3. ps で詳細確認
ps -p $JOB_PID
# 4. SIGTERM で穏便に停止
kill $JOB_PID
# → ジョブが Terminated と表示される
# 5. nohup でターミナル切断耐性のあるプロセスを起動
nohup sleep 1000 > sleep.log 2>&1 &
# ↓ $! は前述の通り「直前の & のプロセスの PID」。ここでは nohup で起動した sleep の PID。
echo "nohup PID: $!"
# このターミナルを閉じても、上のsleepは動き続ける
# 6. 上のプロセスを名前で特定して止める
ps aux | grep "sleep 1000" | grep -v grep
pkill -f "sleep 1000"
# 7. top で 5秒間観察 → q で抜ける
top # M キーでメモリ順、P キーでCPU順、q で終了
# 8. メモリを食ってるプロセスTOP5
ps aux | sort -nrk 4 | head -5 # 4列目=%MEM で数値逆順
# 9. ポート使用状況を見る
lsof -iTCP -sTCP:LISTEN -n -P | head締め: 振り返り(10分)
1. セッション録画を終了
exit2. 今日の発見
このノートに追記:
- ps と top の使い分けがしっくりきた瞬間:
psは静止画、topはリアルタイム
- SIGTERM と SIGKILL の違いが腑に落ちた例:
SIGTERMはプロセス側で止める
SIGKILLはOS側で止める、かなり強力
- 「これ知らずに本番触ったら詰んでた」と思った話:
nohupを使いこなせば、安易にプロセスを修了する事故は避けれそう
- 明日やりたいこと:
カリキュラムの質がかなり悪いので改善したい
マジで説明がわかりづらいし、解説が下手
チェックリスト
-
ps auxの各列の意味を説明できる -
ps aux | grep <name>でプロセスを探せる -
topを起動して CPU/メモリ順にソートできる -
kill <PID>とkill -9 <PID>の違いを説明できる -
Ctrl+Zで一時停止 →bgでバックグラウンド再開 ができた -
nohup ... &でターミナル切断後も動くプロセスを起動した -
lsof -i :<port>でポートを掴んでるプロセスを特定した
詰まった時のチートシート
| やりたいこと | コマンド |
|---|---|
| 全プロセス表示 | ps aux または ps -ef |
| 名前で検索 | ps aux | grep <name> | grep -v grep |
| リアルタイム監視 | top または htop |
| 終了要求(穏便) | kill <PID> |
| 強制終了 | kill -9 <PID> |
| 名前で kill | pkill -f "<pattern>" |
| バックグラウンド起動 | command & |
| 一時停止 | Ctrl+Z |
| ジョブ一覧 | jobs |
| FGに戻す | fg %1 |
| BGで再開 | bg %1 |
| 中断 | Ctrl+C (SIGINT) |
| ターミナル切断耐性 | nohup command > log 2>&1 & |
| シェルから切り離す | disown %1 |
| ポート使用プロセス | lsof -i :<port> |
| メモリ使用量Top | ps aux | sort -nrk 4 | head |
「実務OK」基準
- 「サーバーが重い」と言われた瞬間に top を立ち上げる反射神経
- kill する前に必ず
psで対象PIDを目視確認するクセ -9は最終手段だと身体で覚えているssh 接続切ったら止まったを経験して、nohup / disown / systemd の使い分けが見えるPort already in useを見たら lsof で犯人特定 → kill ができる
ここまで来れば「Linuxの基本操作で困らない人」。明日 Day7 で Level 1 の総復習 をして、実践課題(ログ集計ワンライナー)で身についた力を確認する。
アンチパターン / 初心者やらかし事例
NG 1: いきなり kill -9 連打
kill -9 <PID> # 初手 SIGKILL→ アプリは「終わりの後片付け(バッファ flush、ファイルクローズ、DB セッション切断)」をする猶予がなく、データ破損のリスクがある。
→ 対策: まず kill <PID>(SIGTERM)、数秒待っても消えなければ kill -9。
NG 2: ps aux | grep nginx で grep 自身が引っかかる
ps aux | grep nginx
# nginx 12345 ...
# user 99999 grep nginx ← この行→ 対策: ps aux | grep [n]ginx のように 正規表現で自分を除外。または pgrep nginx。
NG 3: SSH 切ったらスクリプトが止まる
./long_task.sh # ターミナル閉じたら SIGHUP で死ぬ→ 対策: nohup ./long_task.sh > out.log 2>&1 & で SIGHUP を無視させる。本格運用は Day 11 systemd に任せる。
NG 4: pkill -f node で別アプリまで殺す
pkill -f node # `node` を含む全プロセスが死ぬ。VSCode の node も巻き添え→ 対策: 文字列マッチは 十分に長い・固有のパターン に: pkill -f "node /usr/local/myapp/server.js"。
自己評価チェックリスト
-
ps auxの主要列(PID / %CPU / %MEM / COMMAND)を即答できる -
topでM(メモリ順)/P(CPU 順)で並べ替えられる - SIGTERM と SIGKILL の 「アプリに後片付けさせるかどうか」 の差を説明できる
-
command &/Ctrl+Z/bg/fg/jobsを一通り使った -
lsof -i :3000で「ポートを掴んでるプロセス」を見つけられる
次のレッスン: Day 7 - Level 1 総復習
明日は Day 1〜6 をすべて統合する実践課題 デー。
「アクセスログから5xx エラー上位 IP トップ10を抽出して report.txt に保存」みたいな、6日分の道具を全部繋ぐワンライナーで卒業試験を行う。詰まったら各章に戻ってOK。