CPU バスロックのトラブルシューティング

このドキュメントでは、Linux ゲスト オペレーティング システムで CPU バスロックを特定してトラブルシューティングする方法について説明します。CPU バスロックの症状、カーネルログ メッセージを使用してこれらの問題を診断する方法、障害のあるコードを特定する方法、軽減策や修正を適用する方法について説明します。

概要

CPU バスロックは、プロセッサがハードウェア LOCK# 信号をアサートして、システム全体のメモリバスへの排他的アクセスを取得する必要がある場合に発生します。この問題は通常、次のいずれかの状況で発生します。

  • アトミック命令は、キャッシュライン境界を越えるアラインされていないメモリ(スプリット ロック)で動作します。
  • アトミック命令は、uncacheable(UC)として指定されたメモリ(Memory-Mapped I/O(MMIO)など)で動作します。

CPU がグローバル バスロックをアサートするため、ゲスト オペレーティング システム内の他のすべてのプロセッサとデバイスは、メモリ オペレーションが完了するまで待機する必要があります。バスロックの頻度が高いと、CPU のパフォーマンスが大幅に低下する可能性があります。

古いプロセッサはバスロックを追跡しませんでしたが、Intel Sapphire Rapids 以降や AMD Zen 5 以降などの最新の x86 プロセッサには、CPU バスロックを検出するハードウェア機能が含まれています。命令が CPU バスロックをトリガーすると、CPU は命令の完了直後にデバッグ例外(#DB)を発行します。

Intel の Linux カーネル バージョン 5.13 以降、および AMD の 6.13 以降では、Linux カーネルがこの #DB 例外をインターセプトし、通常は障害のあるプロセスをレート制限することで、緩和策を適用します。スレッドを意図的にスリープ状態にすることで、カーネルは単一のアプリケーションがメモリバスを飽和状態にすることを防ぎ、障害のあるアプリケーションのパフォーマンスを犠牲にして、残りのコンピューティング インスタンスのシステム パフォーマンスを維持します。

現象

Linux ゲストのプロセスが CPU バスロックをトリガーしている場合、次の症状が発生することがあります。

  • アプリケーションのパフォーマンスの低下: CPU バスロックにより、アプリケーションに予期しないレイテンシが発生する可能性があります。
  • システム全体の負荷の急増: システム全体の応答性が低下する可能性があります。
  • 予期しないアプリケーションのクラッシュ: 分割ロックまたはバスロックを厳密に処理するようにカーネルを構成した場合(split_lock_detect=fatal)、欠陥のあるアプリケーションが SIGBUS エラーでクラッシュする可能性があります。

CPU バスロックを特定する

コンピューティング インスタンスで CPU バスロックが発生しているかどうかを特定するには、次のいずれかを行います。

  • コンピューティング インスタンスでシリアルポート出力ロギングを有効にしている場合は、シリアルポート出力を確認して、CPU バスロック トレースを探します。
  • コンピューティング インスタンスのオペレーティング システム ログ(/var/log/messages)で、CPU バスロックのトレースを確認します。

CPU バスロックのトレース例

x86/split lock detection: #DB: <process_name>/<pid> took a bus_lock trap at address: 0x<address>

今後の CPU バスロックを検出するには、次の操作を行います。

  1. シリアルポート出力ロギングを有効にします。
  2. 次のログに対してログベースのアラート ポリシーを作成します。

    resource.type="gce_instance" log_id("serialconsole.googleapis.com/serial_port_1_output") textPayload=~"took a bus_lock trap"

    このログエントリには、CPU バスロックの原因となったプロセス名(<process_name>)とプロセス ID(<pid>)、障害が発生した命令ポインタ アドレスが示されています。

CPU バスロックのトラブルシューティング

欠陥のあるアプリケーションを開発またはコンパイルしている場合は、特定の C または C++ コンパイラ警告を使用して、スプリット ロックの原因となる可能性のある変数と構造体を特定できます。

コンパイラの警告

GCC または Clang を使用する場合は、次のフラグを使用してコードをコンパイルし、アライメントの問題を特定します。

  • -Wcast-align または -Wcast-align=strict: ポインタ キャストによってターゲットの必要なアライメントが増加した場合に警告します。汎用 char* バッファを uint64_t* にキャストしてアトミック オペレーションを実行すると、スプリット ロックの典型的な原因になります。
  • -Waddress-of-packed-member: このフラグは、パックされた構造体のメンバーのアドレスを取得したときに警告します(#pragma pack(1) や __attribute__((packed)) を使用するなど)。パックされた構造体は自然なメモリ アライメントを無視するため、パックされた構造体のメンバーに対するアトミック オペレーションは、64 バイトのキャッシュライン境界を越える可能性が高くなります。

キャッシュ不可(UC)メモリロックのキャッチ

キャッシュ不可能なメモリに対するアトミック オペレーションが CPU バスロックを引き起こした場合、コンパイラの警告では検出されません。この問題は通常、デバイスのメモリを操作するときに発生します。

  • メモリ マッピングを監査する: O_SYNC などのフラグを使用した mmap の使用や、/dev/mem または /dev/uio への直接アクセスについてコードを確認します。
  • MMIO でのアトミックを回避: デバイス レジスタまたはキャッシュ不可メモリバッファにマッピングされたメモリ領域で __sync_fetch_and_add や std::atomic などのアトミック オペレーションを使用しないでください。

CPU バスロックの修正

CPU バスロックの問題は、アプリケーションのソースコードでメモリアライメントを修正することで解決できます。

  • アトミック変数、ミューテックス、スピンロックを含む構造体で #pragma pack または __attribute__((packed)) を使用しないでください。
  • 標準のアライメント ディレクティブ(C++11 の alignas(64) や C の __attribute__((aligned(64))) など)を使用して、アトミック オペレーションで頻繁に使用される変数をキャッシュライン境界に強制的に配置します。
  • コンパイル時にアライメント関連の警告がないことを確認します。
  • 標準のロック メカニズム(ミューテックス、スピンロック)またはアトミック命令は、標準のキャッシュ可能な RAM でのみ使用し、MMIO または UC メモリでは使用しないようにしてください。

トラブルシューティングの手順で問題が解決しない場合は、Cloud カスタマーケアにお問い合わせください。トラブルシューティングで収集したすべての情報を含めてください。