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

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

概要

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

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

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_addstd::atomic などのアトミック オペレーションを使用しないでください。

CPU バスロックの修正

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

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

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