排解 CPU 匯流排鎖定問題

本文說明如何找出並排解 Linux 客戶作業系統中的 CPU 匯流排鎖定問題。本文說明 CPU 匯流排鎖定的症狀、如何使用核心記錄訊息診斷這些問題、如何找出有問題的程式碼,以及如何套用緩解措施或修正問題。

總覽

當處理器必須判斷硬體 LOCK# 信號,才能取得系統記憶體匯流排的專屬存取權時,就會發生 CPU 匯流排鎖定。發生這個問題的常見原因如下:

  • 原子指令會對跨越快取行界線的未對齊記憶體執行作業 (分割鎖定)。
  • 原子指令會在指定為 uncacheable (UC) 的記憶體上運作,例如 Memory-Mapped I/O (MMIO)。

由於 CPU 會聲明全域匯流排鎖定,因此客體作業系統中的所有其他處理器和裝置都必須等待記憶體作業完成。如果匯流排鎖定率偏高,CPU 效能可能會大幅降低。

舊版處理器不會追蹤匯流排鎖定,但新版 x86 處理器 (例如 Intel Sapphire Rapids 以上版本,或 AMD Zen 5 以上版本) 內建硬體功能,可偵測 CPU 匯流排鎖定。當指令觸發 CPU 匯流排鎖定時,CPU 會在指令完成後立即發出偵錯例外狀況 (#DB)。

從 Intel 的 Linux 核心版本 5.13 和 AMD 的 Linux 核心版本 6.13 開始,Linux 核心會攔截這項 #DB 例外狀況並套用緩解措施,通常是限制錯誤程序的速率。透過刻意強制執行緒進入休眠狀態,核心可防止單一應用程式耗盡記憶體匯流排,以犧牲有問題的應用程式效能為代價,為其餘運算執行個體保留系統效能。

問題

如果 Linux 客戶端的程序觸發 CPU 匯流排鎖定,可能會出現下列症狀:

  • 應用程式效能降低:CPU 匯流排鎖定可能會導致應用程式出現非預期的延遲。
  • 系統負載大幅增加:整體系統回應速度可能會變慢。
  • 應用程式意外當機:如果將核心設定為嚴格處理分割或匯流排鎖定 (split_lock_detect=fatal),則有問題的應用程式可能會當機並顯示 SIGBUS 錯誤。

找出 CPU 匯流排鎖定

如要判斷運算執行個體是否發生 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)))。由於封裝結構體會忽略自然記憶體對齊方式,因此對封裝結構體成員執行的任何 Atomic 作業,都很可能跨越 64 位元組的快取行界線。

擷取無法快取 (UC) 的記憶體鎖定

如果對無法快取的記憶體執行不可分割的作業,導致 CPU 匯流排鎖定,編譯器警告不會偵測到此情況。與裝置記憶體互動時,通常會發生這個問題:

  • 稽核記憶體對應:檢查程式碼是否使用 mmap,並搭配 O_SYNC 等標記,或是直接存取 /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 Customer Care 團隊聯絡,並提供您在疑難排解期間收集的所有資訊。