C
OS/上級/Lesson 05

ファイルシステム・スケジューリング・応用編 — IPC・シグナル・cgroup・観測性

60分·theory

ファイルシステム・スケジューリング・応用編 — IPC・シグナル・cgroup・観測性

🎯 このレッスンを読み終えたら

このレッスンを読み終えると、以下の 3 つを自信を持って説明できるようになります。

  • ✅ コンテキストスイッチのコスト(スレッド < プロセス)
  • ✅ epoll · kqueue · IOCP によるイベント駆動 I/O
  • ✅ Copy-on-Write + fork() の動作

学習目標をチェックリストとして持ち、すべて答えられるようになったらレッスンを閉じましょう。

ファイルシステム — inode とディレクトリ

一言で言うと: ファイル = inode + データブロック、ディレクトリ = 名前 → inode マッピング

ファイルのオープンから読み取りまで 6 ステップ:
1. open("/etc/hosts") — パスの解析
2. dentry キャッシュ参照 — ディレクトリエントリキャッシュ(RAM)
3. inode のロード — パーミッション・サイズ・ブロック位置・タイムスタンプのメタデータ
4. パーミッションチェック — ユーザー/グループ・rwx
5. fd の割り当て — ファイルディスクリプタ番号を返す(0,1,2 = stdin/stdout/stderr、3 以上 = ユーザー)
6. read(fd, buf, n) — ブロックをページキャッシュ → ユーザーバッファへ

ファイルシステムの種類:

FS用途特徴
ext4Linux 標準安定・汎用
XFS大容量サーバー高性能(RHEL デフォルト)
btrfs次世代スナップショット・チェックサム
ZFSデータ完全性Solaris・FreeBSD 由来
APFSmacOSSSD 最適化・スナップショット
NTFSWindowsパーミッション・ジャーナリング

よくある落とし穴:

  • ❌ 「too many open files」— fd 不足(ulimit -n を増やす)
  • ❌ ファイルへの同時書き込み → データ破損(fcntl ロック・O_APPEND)
  • ❌ シンボリックリンクをたどって無限ループ(-L オプションに注意)
  • fsync() を呼び出してディスク同期を保証(DB・write-ahead log)

CPU スケジューリング — 誰が先に実行されるか

Linux CFS(Completely Fair Scheduler) — デフォルトスケジューラ(2.6.23 以降)。

原理: 各プロセスの 仮想ランタイム を追跡。最も実行時間が少ないプロセスを優先 → 公平性の確保。

スケジューリングポリシー:

ポリシー用途優先度の仕組み
SCHED_NORMAL一般プロセスnice 値(-20 〜 +19)
SCHED_FIFOリアルタイム優先度 + FIFO
SCHED_RRリアルタイム優先度 + ラウンドロビン
SCHED_IDLEバックグラウンド他に実行するものがないときのみ

ラウンドロビン(RR)の動作:
1. 各プロセスに タイムスライス(例: 10 ms)を割り当て
2. スライスが切れるとキューの末尾へ
3. 次のプロセスを実行
公平 だがコンテキストスイッチのコストが発生

スケジューリングの落とし穴:

  • ❌ nice 値の誤解(値が低いほど優先度が高い)— 直感と逆
  • ❌ リアルタイム SCHED_FIFO の無限ループ → システム全体が停止
  • ❌ CPU アフィニティ未設定 → キャッシュ無効化が頻発

最新トレンド:

  • EEVDF(Earliest Eligible Virtual Deadline First)— Linux 6.6+ における CFS の後継
  • Pluggable — sched_ext で BPF スケジューラをカスタマイズ可能(2024 年〜)

IPC・シグナル・ゾンビ — プロセス間通信

IPC(Inter-Process Communication) の種類:

方式速度用途
パイプ親子プロセス間の単方向通信(シェルの \
名前付きパイプ(FIFO)無関係なプロセス間(mkfifo
共有メモリ最高速DB キャッシュ・ゲーム(ゼロコピー)
メッセージキューSystem V・POSIX
セマフォ速い同期のみ(データなし)
ソケットTCP/UDP/Unix ドメイン(最も汎用的)
シグナル速い単純な通知(SIGTERM など)

シグナル の活用:

シグナル意味用途
SIGTERM (15)正常終了要求kill <PID> のデフォルト
SIGKILL (9)強制終了(ハンドラなし)kill -9
SIGINT (2)割り込み(Ctrl+C)インタラクティブな終了
SIGHUP (1)ハングアップ / 設定リロードnginx reload
SIGCHLD子プロセス終了通知ゾンビの回収
SIGUSR1/2ユーザー定義アプリ設定の再読み込み

グレースフルシャットダウン パターン:

code
SIGTERM 受信 → 新規リクエストを拒否 → 処理中のリクエスト完了を待機 → リソース解放 → exit

ゾンビプロセス:

  • 子プロセスが終了したが、親が wait() を呼んでいない → PCB だけが残った状態
  • psZ 状態として表示される
  • 防止策: 親が SIGCHLD ハンドラを登録して waitpid() を呼び出す
  • 孤児プロセス: 親が先に死んだ場合、init(PID 1)が引き取り → init が wait → クリーンアップ

cgroup + 観測性 — コンテナの基盤

cgroup(Control Group) — Linux がプロセスグループのリソースを制限する仕組み。Docker・Kubernetes のコア技術。

利用例:

リソース制限方法
CPUcpu.cfs_quota_us — 100 ms 中 50 ms のみ → 0.5 コア
メモリmemory.limit_in_bytes — 超過時に OOM Kill
I/Oio.weight — ディスク帯域幅
ネットワークnet_cls + tc — トラフィック制御
デバイスdevices.allow — 特定のデバイスへのアクセスのみ許可

Docker の例: docker run --memory=1g --cpus=0.5 nginx は内部的に cgroup を設定しています。

cgroup v2(モダン Linux): 統合された階層構造(v1 の分離されたコントローラを統合)。

観測性(Observability)の 3 本柱:

ツール用途
MetricsPrometheus · Grafana時系列データ(CPU・メモリ・リクエスト数)
LogsLoki · ELK · Splunkテキストイベント
TracesJaeger · Tempo · Zipkin分散トレーシング

eBPF(Extended Berkeley Packet Filter):

  • カーネル内で安全にコードを実行(カーネルパッチ不要)
  • システムコール・ネットワーク・ディスクのあらゆるイベントを観測可能
  • ツール: bcc · bpftrace · pixie · falco(セキュリティ)
  • Cilium(K8s ネットワーク)、Datadog APM もいずれも eBPF ベース

観測性の落とし穴:

  • ❌ メトリクスのみ(原因が分からない)→ トレースも追加
  • ❌ ログ爆発(10 TB/日)→ サンプリング・フィルタリング
  • ❌ /var/log がいっぱいになる → ログローテーションが必須(logrotate)
💻 📌 シナリオで学ぶシステム観測
# ============================================================
# シナリオ 1: "ディスク容量不足アラーム"
# ============================================================
df -h                              # マウントポイント別の使用量
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/sda1        50G   42G  6G   88% /
# tmpfs           3.9G  100M 3.8G   3% /run

# どのディレクトリが大きいか — Top 10
du -sh /var/* 2>/dev/null | sort -h | tail -10
# /var/log    が大きい場合 → ログローテーションが機能していない
# /var/lib/docker が大きい場合 → 不要なイメージ・コンテナのクリーンアップ
docker system prune -a             # Docker のクリーンアップ

# inode 不足もチェック (小さいファイルが多い場合)
df -i

# 1GB 以上の大きいファイルを探す
sudo find / -type f -size +1G -exec ls -lh {} \; 2>/dev/null

# ============================================================
# シナリオ 2: "ポート衝突 — 'Address already in use'"
# ============================================================
# 3000 ポートを使用しているプロセスを探す
lsof -i :3000
# COMMAND   PID  USER  FD  TYPE  NODE NAME
# node    28391  app   25  IPv4  TCP *:3000 (LISTEN)

# または ss (より高速)
ss -tlnp | grep :3000

# 停止して再起動
lsof -ti :3000 | xargs kill -9     # -t = PID のみ出力

# TIME_WAIT 状態が多すぎる場合 (再利用されない)
ss -tan state time-wait | wc -l
# 1000+ の場合 → カーネルパラメータの調整を検討:
#   net.ipv4.tcp_tw_reuse = 1

# ============================================================
# シナリオ 3: "Graceful shutdown の実装"
# ============================================================
# シグナルハンドラ登録 (Python の例)
cat > graceful.py <<'EOF'
import signal, time, sys

def shutdown(sig, frame):
    print(f"SIGTERM 受信。クリーンアップ中...")
    # 新しいリクエストを拒否・進行中のリクエストを完了・DB 接続をクローズ
    time.sleep(2)
    print("正常終了")
    sys.exit(0)

signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)

print("サーバー開始")
while True: time.sleep(1)
EOF

python graceful.py &
PID=$!
sleep 1
kill -TERM $PID                    # 2秒後に正常終了メッセージ

# Docker・K8s も同じパターン:
#   1. SIGTERM を送信
#   2. terminationGracePeriodSeconds (デフォルト 30秒) 待機
#   3. 終了しない場合 SIGKILL

# ============================================================
# シナリオ 4: "systemd でサービス運用"
# ============================================================
# サービスユニットファイル: /etc/systemd/system/myapp.service
# [Unit]
# Description=My App
# After=network.target
#
# [Service]
# Type=simple
# User=app
# WorkingDirectory=/opt/myapp
# ExecStart=/usr/bin/node server.js
# Restart=always
# RestartSec=5
# OOMScoreAdjust=-500              # 優先保護
# LimitNOFILE=65536                # fd 制限
#
# [Install]
# WantedBy=multi-user.target

# 使用
sudo systemctl daemon-reload       # ユニット修正後必須
sudo systemctl enable myapp        # 起動時に自動開始
sudo systemctl start myapp
sudo systemctl status myapp         # 状態 + 最新ログ
sudo systemctl restart myapp
journalctl -u myapp -f             # ログリアルタイム (tail -f)
journalctl -u myapp --since '1 hour ago'

# ============================================================
# シナリオ 5: "コンテナリソース制限 (cgroup)"
# ============================================================
# Docker が cgroup で処理
docker run --memory=512m --cpus=0.5 --pids-limit=100 myapp
# 内部: /sys/fs/cgroup/memory.max·cpu.max·pids.max に記録

# 直接確認
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/system.slice/docker-<ID>.scope/memory.current

# コンテナリソースリアルタイム
docker stats                       # すべてのコンテナ
docker stats --no-stream myapp     # 一度だけ

# ============================================================
# シナリオ 6: "eBPF でシステムコール統計 (カーネルモード観測)"
# ============================================================
# bcc·bpftrace — コード修正なしでカーネルイベントを追跡
# どのプロセスが open() を頻繁に呼び出すか
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'

# 100ms 以上かかるディスク I/O のみ表示
sudo biolatency-bpfcc

# Python 関数呼び出し追跡
sudo py-spy record --pid 28391 -o profile.svg

# セキュリティ — Falco で疑わしい動作を検出 (root 権限取得・重要ファイルアクセス)

🤖 AI にこう依頼してみましょう

このレッスンの概念を知っていれば、AI に具体的に指示を出せます。曖昧な「直して」ではなく、語彙を持ったリクエスト — それがトークン削減の出発点です。

  • 「この処理がコンテキストスイッチのコストが高いか診断して」
  • 「この処理を epoll(Linux)ベースの非同期に変えるとどんなメリットがあるか教えて」

なぜこれがトークンを減らすのか

概念を知らないと、AI の回答を受け取っても 「それって何ですか?」 と再度聞かなければなりません。その「再質問」がトークンを消費します。概念を一度身につければ、会話が一度で完結します。

ファイルシステム・スケジューリング・高度 - OS