本文档介绍了如何在 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 联系 ,并提供您在问题排查期间收集的所有信息。