1-6. プロセスとジョブ制御 - ps, top, kill, nohup

所要時間: 25-50分(がっつりなら2セッション分) ゴール: 動いているプロセスを観察・操作できる。フリーズしたプロセスを安全に止められる コミット内容: 今日の操作履歴を ~/log/linux_day06.log に保存

この章が終わるとできること

  • ps aux の出力を見て「PID / 所有者 / CPU / MEM / コマンド」を読める
  • top / htopCPU/メモリ食い をリアルタイム特定できる
  • kill <PID>kill -9 <PID>使い分け ができる
  • Ctrl+Zbg / fgnohup ... & でジョブ制御が書ける
  • 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.log

1. プロセスとは何か

# 自分のシェル自体のプロセス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が最初に起動するプロセス)。

プロセスの親子関係(重要)

シェルでコマンドを実行すると:

  1. シェル(zsh)が fork() で自分のコピーを作る
  2. コピーされた子プロセスが exec() で目的のプログラムに変身する
  3. 子プロセスが終わるまでシェルは「待つ」
  4. 子プロセスが終了 → シェルが終了ステータスを受け取る → 次のプロンプトを表示

この仕組みのせいで:

  • 子プロセスは親シェルの環境変数を引き継ぐ(だから 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
%CPUCPU使用率(瞬間値)
%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 auxps -ef
出身BSDSystem V
ハイフン無し有り(-ef
%CPU出る出ない(C 列で近い情報)
%MEM出る出ない
RSS(実メモリ)出る出ない
PPID(親PID)出ない出る
よく見る環境macOS、開発機Linux サーバー、商用 Unix

使い分け

やりたいことコマンド
メモリ・CPU 使用率を見たいps aux
親子関係を追いたいps -ef
とにかく全プロセス見たいどっちでもOK
grep と組み合わせるどっちでもOK

イメージ

  • ps aux = リソース監視向き(重いやつを探す)
  • ps -ef = 系統樹追跡向き(親子関係を追う)

一番覚えやすい説明

  • ps auxCPU/メモリ重視(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 で killF5 でツリー表示 などキー操作が直感的
  • 横スクロールでコマンド全体が見える

本番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がプロセスに送る「通知」のようなもの。シグナルを受け取ったプロセスは、それに応じた動作をする(終了する、設定を再読み込みする、一時停止する等)。

シグナルには番号と名前がある:

主要なシグナル

番号名前意味挙動
1SIGHUPHang Upターミナル切断(設定再読み込みに使う場合も)
2SIGINTInterruptCtrl+C を押した時に送られる
9SIGKILLKill強制終了(プロセスは無視できない、即死)
15SIGTERMTerminate通常の終了要求(プロセスが受け取って後始末できる)
17/19SIGSTOPStop一時停止(無視できない)
18SIGCONTContinue再開
20SIGTSTPTerminal StopCtrl+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 $!    # 直前にバックグラウンド起動したプロセスのPID

fg 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+Zbg = フォアグラウンドのやつを裏に回す
  • fg %1 = 裏や停止中のやつを前に戻す
  • ジョブ番号 %1 は PID とは別物(シェル内だけの通し番号)

ジョブ制御の実務ユースケース

  • 開発サーバーを動かしながら別作業: npm run dev & でバックグラウンド起動 → コーディング再開
  • 複数の tail -f を切り替えながら見る: 1個目を Ctrl+Ztail -f another.logfg %1 で戻す
  • GUIアプリを起動して即ターミナルに戻る: code . &(VS Code 起動してターミナル即解放)

6. nohup と disown - ターミナル切断後も動かす

nohupdisown「ターミナル(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 の違い

ざっくり言うと

普通に & で動かしただけだと、ターミナルを閉じたときに一緒に死ぬ可能性がある。 それを防ぐのが nohupdisown

まず普通のパターン

./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 = このジョブ、もうシェルの管理対象じゃないことにする

違い

& のみnohupdisown
いつ使う?一時的に裏で動かす起動する前起動した後
ターミナル切断耐性なしあり(最初から)あり(後から付与)
出力端末に流れる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 devError: 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. セッション録画を終了

exit

2. 今日の発見

このノートに追記:

- 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>
名前で killpkill -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>
メモリ使用量Topps 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 nginxgrep 自身が引っかかる

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)を即答できる
  • topM(メモリ順)/ 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。

Day 7: Level 1 総復習