Fehlerbehebung bei CPU-Bus-Sperren

In diesem Dokument wird beschrieben, wie Sie CPU-Bus-Sperren in Linux-Gastbetriebssystemen identifizieren 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 beschrieben.

Ü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 in der Regel in einem der folgenden Fälle auf:

  • Eine atomare Anweisung wird für nicht ausgerichteten Speicher ausgeführt, der eine Cachezeilengrenze überschreitet (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-Locks kann die CPU-Leistung erheblich beeinträchtigen.

Bei älteren Prozessoren wurden Bus-Sperren nicht erfasst. Moderne x86-Prozessoren wie Intel Sapphire Rapids und höher oder AMD Zen 5 und höher verfügen jedoch über eine Hardwarefunktion, die CPU-Bus-Sperren erkennt. Wenn eine Anweisung eine CPU-Bus-Sperre auslöst, gibt die CPU sofort 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. Indem der Thread absichtlich in den Ruhezustand versetzt wird, verhindert der Kernel, dass eine einzelne Anwendung den Speicherbus überlastet. So wird die Systemleistung für den Rest der Compute-Instanz aufrechterhalten, allerdings auf Kosten der Leistung der fehlerhaften Anwendung.

Symptome

Wenn ein Prozess in Ihrem Linux-Gastsystem 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 Abstürze von Anwendungen: 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 erstellen für das folgende Log:

    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 Casten eines generischen char*-Puffers in einen uint64_t* 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) Arbeitsspeicher-Locks erkennen

Wenn atomare Operationen auf nicht im Cache speicherbaren Speicher den CPU-Bus sperren, werden Compilerwarnungen nicht ausgegeben. Dieses Problem tritt normalerweise auf, wenn Sie mit dem Gerätespeicher interagieren:

  • Speicherzuordnungen prüfen: Suchen Sie in Ihrem Code 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 der CPU-Bus-Sperre 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 (z. B. 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 werden konnte, wenden Sie sich an Cloud Customer Care und geben Sie alle Informationen an, die Sie bei der Fehlerbehebung gesammelt haben.