Resolver problemas de bloqueios de barramento da CPU

Este documento explica como identificar e resolver problemas de bloqueios de barramento da CPU em sistemas operacionais convidados do Linux. Ele aborda os sintomas de bloqueios de barramento da CPU, como diagnosticar esses problemas usando mensagens de registro do kernel, como localizar o código com falha e como aplicar mitigações ou correções.

Visão geral

Um bloqueio de barramento da CPU ocorre quando um processador precisa declarar um sinal de hardware LOCK# para adquirir acesso exclusivo ao barramento de memória em todo o sistema. Esse problema geralmente acontece em uma das seguintes circunstâncias:

  • Uma instrução atômica opera em memória desalinhada que cruza um limite de linha de cache (um bloqueio dividido).
  • Uma instrução atômica opera na memória designada como uncacheable (UC), como Memory-Mapped I/O (MMIO).

Como a CPU declara um bloqueio de barramento global, todos os outros processadores e dispositivos no sistema operacional convidado precisam esperar até que a operação de memória seja concluída. Uma alta taxa de bloqueios de barramento pode prejudicar muito o desempenho da CPU.

Embora os processadores mais antigos não rastreassem bloqueios de barramento, os processadores x86 modernos, como Intel Sapphire Rapids e mais recentes ou AMD Zen 5 e mais recentes, incluem um recurso de hardware que detecta bloqueios de barramento da CPU. Quando uma instrução aciona um bloqueio do barramento da CPU, ela emite uma exceção de depuração (#DB) imediatamente após a conclusão da instrução.

A partir da versão 5.13 do kernel do Linux em Intel e 6.13 em AMD, o kernel do Linux intercepta essa exceção #DB e aplica uma mitigação, geralmente limitando a taxa do processo com falha. Ao forçar intencionalmente a suspensão da execução da linha de execução, o kernel impede que um único aplicativo sature o barramento de memória, preservando o desempenho do sistema para o restante da instância de computação ao custo do desempenho do aplicativo com falha.

Sintomas

Se um processo no seu convidado Linux estiver acionando bloqueios de barramento da CPU, você poderá ter os seguintes sintomas:

  • Desempenho degradado do aplicativo: os bloqueios de barramento da CPU podem introduzir latência inesperada para aplicativos.
  • Picos de carga em todo o sistema: a capacidade de resposta geral do sistema pode diminuir.
  • Falhas inesperadas de aplicativos: se você configurar o kernel para processar bloqueios divididos ou de barramento de forma estrita (split_lock_detect=fatal), o aplicativo com falha poderá falhar com um erro SIGBUS.

Identificar bloqueios de barramento da CPU

Para identificar se a instância de computação está sofrendo bloqueios de barramento da CPU, faça uma das seguintes ações:

  • Se você ativou o registro de saída da porta serial para sua instância de computação, analise a saída da porta serial para um rastreamento de bloqueio do barramento da CPU.
  • Analise os registros do sistema operacional da sua instância de computação (/var/log/messages) para um rastreamento de bloqueio do barramento da CPU.

Exemplo de rastreamento de bloqueio do barramento da CPU

x86/split lock detection: #DB: <process_name>/<pid> took a bus_lock trap at address: 0x<address>

Para detectar bloqueios futuros do barramento da CPU, faça o seguinte:

  1. Ative a geração de registros de saída da porta serial.
  2. Crie uma política de alertas baseada em registros para o seguinte registro:

    resource.type="gce_instance" log_id("serialconsole.googleapis.com/serial_port_1_output") textPayload=~"took a bus_lock trap"

    Essa entrada de registro fornece o nome do processo (<process_name>) e o ID do processo (<pid>) responsável pelo bloqueio do barramento da CPU, bem como o endereço do ponteiro de instrução em que a falha ocorreu.

Resolver problemas de bloqueios de barramento da CPU

Se você estiver desenvolvendo ou compilando o aplicativo com falha, use avisos específicos do compilador C ou C++ para identificar variáveis e estruturas que podem causar bloqueios divididos.

Avisos do compilador

Se você usa GCC ou Clang, compile seu código com as seguintes flags para ajudar a identificar problemas de alinhamento:

  • -Wcast-align ou -Wcast-align=strict: essas flags avisam quando uma conversão de ponteiro aumenta o alinhamento necessário do destino. Fazer o casting de um buffer char* genérico para um uint64_t* e realizar uma operação atômica nele é uma causa clássica de bloqueios divididos.
  • -Waddress-of-packed-member: essa flag avisa quando você usa o endereço de um membro de struct compactado (por exemplo, usando #pragma pack(1) ou __attribute__((packed))). Como as estruturas compactadas ignoram o alinhamento natural de memória, qualquer operação atômica em um membro de uma struct compactada tem uma alta probabilidade de cruzar um limite de linha de cache de 64 bytes.

Como detectar bloqueios de memória não armazenáveis em cache (UC)

Se as operações atômicas em memória não armazenável em cache causarem o bloqueio do barramento da CPU, os avisos do compilador não vão detectar isso. Esse problema geralmente acontece ao interagir com a memória do dispositivo:

  • Auditoria de mapeamentos de memória: revise seu código para usos de mmap com flags como O_SYNC ou acesso direto a /dev/mem ou /dev/uio.
  • Evite operações atômicas em MMIO: não use operações atômicas como __sync_fetch_and_add ou std::atomic em regiões de memória mapeadas para registros de dispositivos ou buffers de memória não armazenáveis em cache.

Como corrigir bloqueios de barramento da CPU

Para corrigir problemas de bloqueio do barramento da CPU, corrija o alinhamento da memória no código-fonte do aplicativo.

  • Evite usar #pragma pack ou __attribute__((packed)) em estruturas que contenham variáveis atômicas, mutexes ou spinlocks.
  • Use diretivas de alinhamento padrão (como alignas(64) em C++11 ou __attribute__((aligned(64))) em C) para forçar variáveis muito usadas em operações atômicas a se alinharem aos limites da linha de cache.
  • Verifique se não há avisos relacionados ao alinhamento durante a compilação.
  • Use apenas mecanismos de bloqueio padrão (mutexes, spinlocks) ou instruções atômicas em RAM padrão e armazenável em cache, nunca em memória MMIO ou UC.

Se as etapas de solução de problemas não resolverem o problema, entre em contato com o Cloud Customer Care e inclua todas as informações coletadas durante a solução de problemas.