이 문서에서는 Linux 게스트 운영체제에서 CPU 버스 잠금 을 식별하고 문제 해결하는 방법을 설명합니다. CPU 버스 잠금의 증상, 커널 로그 메시지를 사용하여 이러한 문제를 진단하는 방법, 결함이 있는 코드를 찾는 방법, 완화 또는 수정사항을 적용하는 방법을 다룹니다.
개요
프로세서가 시스템 전체 메모리 버스에 대한 독점 액세스 권한을 얻기 위해 하드웨어 LOCK# 신호를 어설션해야 하는 경우 CPU 버스 잠금이 발생합니다. 이 문제는 일반적으로 다음 상황 중 하나에서 발생합니다.
- 원자적 명령어는 캐시 라인 경계 (분할 잠금)를 교차하는 정렬되지 않은 메모리에서 작동합니다.
- 원자적 명령어는
Memory-Mapped I/O(MMIO)와 같이uncacheable(UC)로 지정된 메모리에서 작동합니다.
CPU가 전역 버스 잠금을 어설션하므로 게스트 운영체제의 다른 모든 프로세서와 기기는 메모리 작업이 완료될 때까지 기다려야 합니다. 버스 잠금 비율이 높으면 CPU 성능이 크게 저하될 수 있습니다.
이전 프로세서는 버스 잠금을 추적하지 않았지만 Intel Sapphire Rapids 이상 또는 AMD Zen 5 이상과 같은 최신 x86 프로세서에는 CPU 버스 잠금을 감지하는 하드웨어 기능이 포함되어 있습니다. 명령어가 CPU 버스 잠금을 트리거하면 CPU는 명령어 완료 직후 디버그 예외 (#DB)를 발생시킵니다.
Intel의 Linux 커널 버전 5.13 및 AMD의 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 버스 잠금이 발생하면 컴파일러 경고가 이를 포착하지 않습니다. 이 문제는 일반적으로 기기 메모리와 상호작용할 때 발생합니다.
- 메모리 매핑 감사:
O_SYNC와 같은 플래그가 있는mmap또는/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에 문의 하고 문제 해결 중에 수집한 모든 정보를 포함하세요.