Fehlerbehebung bei CPU-Bus-Sperren

In diesem Dokument wird beschrieben, wie Sie CPU-Bus-Sperren in Linux-Gastbetriebssystemen erkennen und beheben. Es werden die Symptome von CPU-Bus-Sperren, die Diagnose dieser Probleme mithilfe von Kernel-Logmeldungen, die Suche nach dem fehlerhaften Code und die Anwendung von Maßnahmen oder Korrekturen behandelt.

Übersicht

Eine CPU-Bus-Sperre tritt auf, wenn ein Prozessor ein Hardware-LOCK#-Signal senden muss, um exklusiven Zugriff auf den systemweiten Speicherbus zu erhalten. Dieses Problem tritt normalerweise in einem der folgenden Fälle auf:

  • Eine atomare Anweisung wird für nicht ausgerichteten Speicher ausgeführt, der eine Cachezeilengrenze überschreitet (ein Split-Lock).
  • Eine atomare Anweisung wird für Speicher ausgeführt, der als uncacheable (UC) gekennzeichnet ist, z. B. Memory-Mapped I/O (MMIO).

Da die CPU eine globale Bussperre durchsetzt, müssen alle anderen Prozessoren und Geräte im Gastbetriebssystem warten, bis der Speichervorgang abgeschlossen ist. Eine hohe Anzahl von Bus-Sperren kann die CPU-Leistung erheblich beeinträchtigen.

Bei älteren Prozessoren wurden Bus-Locks nicht erfasst. Moderne x86-Prozessoren wie Intel Sapphire Rapids und höher oder AMD Zen 5 und höher verfügen über eine Hardwarefunktion, die CPU-Bus-Locks erkennt. Wenn eine Anweisung eine CPU-Bus-Sperre auslöst, gibt die CPU unmittelbar nach Abschluss der Anweisung eine Debug-Ausnahme (#DB) aus.

Ab Linux-Kernelversion 5.13 auf Intel und 6.13 auf AMD fängt der Linux-Kernel diese #DB-Ausnahme ab und wendet eine Gegenmaßnahme an, in der Regel durch Ratenbegrenzung des fehlerhaften Prozesses. Durch das absichtliche Versetzen des Threads in den Schlafmodus verhindert der Kernel, dass eine einzelne Anwendung den Speicherbus sättigt. So wird die Systemleistung für den Rest der Compute-Instanz auf Kosten der Leistung der fehlerhaften Anwendung aufrechterhalten.

Symptome

Wenn ein Prozess in Ihrem Linux-Gast CPU-Bus-Sperren auslöst, können die folgenden Symptome auftreten:

  • Beeinträchtigte Anwendungsleistung: CPU-Bus-Sperren können zu unerwarteten Latenzen bei Anwendungen führen.
  • Systemweite Lastspitzen: Die Reaktionsfähigkeit des Gesamtsystems kann abnehmen.
  • Unerwartete Anwendungsabstürze: Wenn Sie den Kernel so konfigurieren, dass er Split- oder Bus-Locks streng behandelt (split_lock_detect=fatal), kann die fehlerhafte Anwendung mit einem SIGBUS-Fehler abstürzen.

CPU-Bus-Sperren identifizieren

So stellen Sie fest, ob auf Ihrer Compute-Instanz CPU-Bus-Sperren auftreten:

Beispiel für einen CPU-Bus-Lock-Trace

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

So erkennen Sie zukünftige CPU-Bus-Sperren:

  1. Logging der Ausgabe des seriellen Ports aktivieren
  2. Logbasierte Benachrichtigungsrichtlinie für das folgende Log erstellen:

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

    Dieser Logeintrag enthält den Prozessnamen (<process_name>) und die Prozess-ID (<pid>), die für die CPU-Bus-Sperre verantwortlich sind, sowie die Adresse des Befehlszeigers, an der der Fehler aufgetreten ist.

Fehlerbehebung bei CPU-Bus-Sperren

Wenn Sie die fehlerhafte Anwendung entwickeln oder kompilieren, können Sie bestimmte C- oder C++-Compilerwarnungen verwenden, um Variablen und Strukturen zu identifizieren, die möglicherweise Split Locks verursachen.

Compilerwarnungen

Wenn Sie GCC oder Clang verwenden, kompilieren Sie Ihren Code mit den folgenden Flags, um Ausrichtungsprobleme zu erkennen:

  • -Wcast-align oder -Wcast-align=strict: Diese Flags warnen Sie, wenn durch eine Pointer-Umwandlung die erforderliche Ausrichtung des Ziels erhöht wird. Das Umwandeln eines generischen char*-Puffers in einen uint64_t*-Puffer und das Ausführen eines atomaren Vorgangs darauf ist eine klassische Ursache für Split Locks.
  • -Waddress-of-packed-member: Dieses Flag warnt Sie, wenn Sie die Adresse eines gepackten Strukturmembers abrufen (z. B. mit #pragma pack(1) oder __attribute__((packed))). Da bei gepackten Strukturen die natürliche Speicherausrichtung ignoriert wird, überschreitet jede atomare Operation für ein Member einer gepackten Struktur mit hoher Wahrscheinlichkeit eine 64‑Byte-Cachezeilengrenze.

Nicht cachefähige (UC) Speicherlocks erkennen

Wenn atomare Vorgänge für nicht im Cache speicherbaren Arbeitsspeicher die CPU-Bus-Sperre verursachen, werden sie nicht durch Compilerwarnungen erkannt. Dieses Problem tritt in der Regel auf, wenn Sie mit dem Gerätespeicher interagieren:

  • Speicherzuordnungen prüfen: Sehen Sie sich Ihren Code an und suchen Sie nach Verwendungen von mmap mit Flags wie O_SYNC oder direktem Zugriff auf /dev/mem oder /dev/uio.
  • Vermeiden Sie atomare Operationen auf MMIO: Verwenden Sie keine atomaren Operationen wie __sync_fetch_and_add oder std::atomic für Speicherbereiche, die Geräte Registern oder nicht cachefähigen Speicherpuffern zugeordnet sind.

CPU-Bus-Sperren beheben

Probleme mit CPU-Bus-Sperren können Sie beheben, indem Sie die Speicherausrichtung im Quellcode der Anwendung korrigieren.

  • Vermeiden Sie die Verwendung von #pragma pack oder __attribute__((packed)) für Strukturen, die atomare Variablen, Mutexe oder Spinlocks enthalten.
  • Verwenden Sie Standardausrichtungsanweisungen wie alignas(64) in C++11 oder __attribute__((aligned(64))) in C, um Variablen, die häufig in atomaren Operationen verwendet werden, an Cachezeilengrenzen auszurichten.
  • Achten Sie darauf, dass während der Kompilierung keine Warnungen im Zusammenhang mit der Ausrichtung angezeigt werden.
  • Verwenden Sie nur standardmäßige Sperrmechanismen (Mutexes, Spinlocks) oder atomare Anweisungen für standardmäßigen, cachefähigen RAM, niemals für MMIO- oder UC-Speicher.

Wenn das Problem durch die Schritte zur Fehlerbehebung nicht behoben wurde, wenden Sie sich an Cloud Customer Care und geben Sie alle Informationen an, die Sie bei der Fehlerbehebung gesammelt haben.