本文說明如何找出並排解 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 匯流排鎖定追蹤記錄。
- 查看運算執行個體的作業系統記錄 (
/var/log/messages),瞭解 CPU 匯流排鎖定追蹤記錄。
CPU 匯流排鎖定追蹤記錄範例
x86/split lock detection: #DB: <process_name>/<pid> took a bus_lock trap at
address: 0x<address>
如要偵測未來的 CPU 匯流排鎖定,請按照下列步驟操作:
- 啟用序列埠輸出記錄功能。
建立以記錄為基礎的警告政策,適用於下列記錄:
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 匯流排鎖定,編譯器警告不會偵測到此情況。與裝置記憶體互動時,通常會發生這個問題:
- 稽核記憶體對應:檢查程式碼是否使用
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 團隊聯絡,並附上您在疑難排解期間收集的所有資訊。