このページの目次

同期デバッグ

カバー範囲: lockdep (ロック依存グラフ) → lockstat (競合統計) → KCSAN (データ競合検出) → hung task detector → NMI watchdog → CONFIG_DEBUG_ATOMIC_SLEEP カーネルバージョン: 2.6 ~ 6.x

概要

並行性のバグは、カーネルで最もデバッグが難しい問題の一つです:デッドロック、データ競合、原子コンテキストでのスリープ。これらのバグは非確定的であることが多く、特定のタイミングでしか発生しません。幸いにも、カーネルには強力な同期デバッグインフラストラクチャが組み込まれており、バグが発生した時(場合によっては発生する前)にそれを検出することができます。


Lockdep: ロック依存バリデーター

// kernel/locking/lockdep.c
// 核心思想: 実行時ロック依存グラフを構築し、ループ(デッドロック)をチェックする
//   ロック取得ごとに: グラフに A→B の辺を追加する
//   もし B→A の辺が既に存在する場合 → デッドロック警告を発する!
//
//   実際にデッドロックが発生する必要はない! Lockdep は潜在的なデッドロックを「予測」できる

// Lockdep が追跡する「ロッククラス」:
//   - 同一タイプのロック(例: 全ての inode→i_lock)→ 同一の class
//   - 異なる関数で取得されたロック → 異なる class(タイプが同じでも)

// /proc/lockdep: 完全なロック依存グラフ
// /proc/lockdep_stats: 依存グラフの統計情報

Lockdep 報告の読み方

典型的な lockdep の出力:
  ======================================================
  WARNING: possible circular locking dependency detected
  ======================================================
  CPU 0: lock(A); ... lock(B);
  CPU 1: lock(B); ... lock(A);
  → 潜在的なデッドロック! 実際の実行ではまだ発生していない場合もある

// さらに以下も含まれる:
//   - 各ロックがどの関数、どのファイルで取得されたか
//   - ブート以降、各ロックの保持履歴

Lockstat: 競合統計

// CONFIG_LOCK_STAT
// 各ロックについて記録:
//   - wait time: ロックを待つのにかかった時間
//   - hold time: ロックを保持していた時間 (CONFIG_LOCK_STAT_HOLDLOCK が必要)

// /proc/lock_stat:
//   形式: class_name    contended   total_wait_ns   max_wait_ns   min_wait_ns

KCSAN: カーネル同時実行サニタイザー

// kernel/kcsan/
// ユーザーランドの ThreadSanitizer (TSan) に類似
// 実行時にデータ競合を検出: 2つのCPUが同じメモリアドレスに同時にアクセスし、少なくとも一方が書き込みの場合

// 動作原理:
//   1. 各メモリアクセスに対して: アドレス + サイズ + タイムスタンプを記録 (ウォッチポイント)
//   2. もし別のCPUが同期なしで同じアドレスにアクセスした場合 → 報告

// 報告の読み方:
//   BUG: KCSAN: data-race in function_name (read/write of 4 bytes at addr)
//     CPU 0: func_a() で x に書き込み
//     CPU 1: func_b() で x から読み込み
//     → x のアクセスに共通のロックで保護されていない → データ競合!

// コンパイルオプション: CONFIG_KCSAN=y
//   KCSAN付きのカーネルは大幅に低速になる (~10倍)、デバッグ専用

Hung Task Detector

検出: プロセスが TASK_UNINTERRUPTIBLE (D) 状態で120秒以上経過
  → 通常はデッドロック、NFS 到達不能、またはドライバのバグ

設定:
  /proc/sys/kernel/hung_task_timeout_secs  (デフォルト 120)
  /proc/sys/kernel/hung_task_panic         (0=警告, 1=パニック)

出力: 停止したプロセスのカーネルスタックを出力 (重要!)
  → mutex_lock() などで待機している場合 → そのロックを保持しているプロセスを探す

NMI Watchdog

NMI (非マスク可能割り込み) をベースにしたデッドロック検出:
  各CPUは定期的にNMIを受信し、CPUごとのカウンターをリセットする
  もしあるCPUが長時間NMIを受信しない場合:
    → 割り込みが無効化されている時間が長すぎる可能性がある (spin_lock_irqsave の無限ループなど)
    → 停止したCPUのスタックトレースを出力

動作原理:
  perf event ドライバー → オーバーフロー → NMI → watchdog overflow handler
  → 前回割り込み以降の hrtimer 時間をチェック → 時間超過の場合 → アラート

その他のデバッグツール

// CONFIG_DEBUG_ATOMIC_SLEEP:
//   原子コンテキストで schedule() (またはスリープする可能性のある任意の関数) を呼び出すと
//   → 即座に BUG() を発生させる

// CONFIG_PROVE_LOCKING (lockdep の拡張):
//   各ロック操作が lockdep によってチェックされる → より大きな実行時オーバーヘッド
//   しかし、より多くの不適切なロック使用を検出可能

// CONFIG_DEBUG_SPINLOCK:
//   spinlock の未初期化使用、ダブルアンロックなどをチェック

// CONFIG_PROVE_RCU:
//   RCU リーダーがクリティカルセクション外で rcu_dereference やスリープなどを呼び出すのをチェック

デバッグコマンド早見表

# lockdep: 依存グラフ + 統計
cat /proc/lockdep        # 完全なロック依存グラフ (テキスト形式、非常に大きい)
cat /proc/lockdep_stats  # 統計サマリー

# lockstat: 競合
cat /proc/lock_stat | sort -k2 -n -r | head -20

# hung task: 現在停止しているプロセス
echo t > /proc/sysrq-trigger   # 全プロセスのカーネルスタックを出力 (sysrq)
cat /proc/<pid>/stack           # 単一プロセスのカーネルスタック (5.14以降)

# データ競合 (KCSAN、コンパイル時に有効化が必要)
dmesg | grep KCSAN

# 割り込み無効化のチェック
dmesg | grep -i "sleeping function called from invalid context"

参考

  • カーネルドキュメント⁠: Documentation/locking/lockdep-design.rst, Documentation/dev-tools/kcsan.rst
  • ソースコード⁠: kernel/locking/lockdep.c, kernel/kcsan/, kernel/watchdog.c, kernel/hung_task.c
  • LWN: "Lockdep", "The Kernel Concurrency Sanitizer"

キーワード: lockdep, lockstat, KCSAN, data race, hung task, NMI watchdog, DEBUG_ATOMIC_SLEEP, PROVE_LOCKING