Soluciona problemas de bloqueos de bus de CPU

En este documento, se explica cómo identificar y solucionar problemas relacionados con los bloqueos de bus de CPU en sistemas operativos invitados de Linux. En él, se abordan los síntomas de los bloqueos de bus de CPU, cómo diagnosticar estos problemas con mensajes de registro del kernel, cómo ubicar el código defectuoso y cómo aplicar mitigaciones o correcciones.

Descripción general

Un bloqueo de bus de CPU ocurre cuando un procesador debe confirmar un indicador de hardware LOCK# para adquirir acceso exclusivo al bus de memoria de todo el sistema. Por lo general, este problema ocurre en una de las siguientes circunstancias:

  • Una instrucción atómica opera en memoria no alineada que cruza un límite de línea de caché (un bloqueo dividido).
  • Una instrucción atómica opera en la memoria designada como uncacheable (UC), como Memory-Mapped I/O (MMIO).

Dado que la CPU confirma un bloqueo de bus global, todos los demás procesadores y dispositivos del sistema operativo invitado deben esperar mientras se completa la operación de memoria. Una alta tasa de bloqueos de bus puede degradar gravemente el rendimiento de la CPU.

Si bien los procesadores más antiguos no realizaban un seguimiento de los bloqueos de bus, los procesadores x86 modernos, como Intel Sapphire Rapids y posteriores, o AMD Zen 5 y posteriores, incluyen una función de hardware que detecta los bloqueos de bus de la CPU. Cuando una instrucción activa un bloqueo del bus de la CPU, esta emite una excepción de depuración (#DB) inmediatamente después de que se completa la instrucción.

A partir de la versión 5.13 del kernel de Linux en Intel y la 6.13 en AMD, el kernel de Linux intercepta esta excepción de #DB y aplica una mitigación, por lo general, limitando la velocidad del proceso defectuoso. Al forzar intencionalmente la suspensión del subproceso, el kernel evita que una sola aplicación sature el bus de memoria, lo que preserva el rendimiento del sistema para el resto de la instancia de procesamiento a costa del rendimiento de la aplicación defectuosa.

Síntomas

Si un proceso en tu invitado de Linux activa bloqueos de bus de CPU, es posible que experimentes los siguientes síntomas:

  • Rendimiento degradado de la aplicación: Los bloqueos del bus de la CPU pueden introducir latencia imprevista para las aplicaciones.
  • Aumentos repentinos de carga en todo el sistema: Es posible que disminuya la capacidad de respuesta general del sistema.
  • Bloqueos inesperados de la aplicación: Si configuras el kernel para que controle los bloqueos de división o de bus de forma estricta (split_lock_detect=fatal), es posible que la aplicación defectuosa se bloquee con un error SIGBUS.

Identifica los bloqueos del bus de la CPU

Para identificar si tu instancia de procesamiento experimenta bloqueos de bus de CPU, haz una de las siguientes acciones:

Ejemplo de registro de bloqueo del bus de la CPU

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

Para detectar bloqueos futuros del bus de la CPU, haz lo siguiente:

  1. Habilita el registro de salida del puerto en serie.
  2. Crea una política de alertas basada en registros para el siguiente registro:

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

    Esta entrada de registro te proporciona el nombre del proceso (<process_name>) y el ID del proceso (<pid>) que es responsable del bloqueo del bus de la CPU, así como la dirección del puntero de instrucción en la que se produjo la falla.

Soluciona problemas de bloqueos de bus de CPU

Si estás desarrollando o compilando la aplicación defectuosa, puedes usar advertencias específicas del compilador de C o C++ para identificar variables y estructuras que podrían causar bloqueos divididos.

Advertencias del compilador

Si usas GCC o Clang, compila tu código con las siguientes marcas para identificar problemas de alineación:

  • -Wcast-align o -Wcast-align=strict: Estas marcas te advierten cuando una conversión de puntero aumenta la alineación requerida del destino. Convertir un búfer char* genérico en un uint64_t* y realizar una operación atómica en él es una causa clásica de bloqueos divididos.
  • -Waddress-of-packed-member: Esta marca te advierte cuando tomas la dirección de un miembro de struct empaquetado (por ejemplo, con #pragma pack(1) o __attribute__((packed))). Debido a que las estructuras empaquetadas ignoran la alineación natural de la memoria, cualquier operación atómica en un miembro de una estructura empaquetada tiene una alta probabilidad de cruzar un límite de línea de caché de 64 bytes.

Cómo detectar bloqueos de memoria no almacenables en caché (UC)

Si las operaciones atómicas en la memoria no almacenable en caché provocan el bloqueo del bus de la CPU, las advertencias del compilador no lo detectarán. Este problema suele ocurrir cuando interactúas con la memoria del dispositivo:

  • Audita las asignaciones de memoria: Revisa tu código para ver si se usa mmap con marcas como O_SYNC o acceso directo a /dev/mem o /dev/uio.
  • Evita las operaciones atómicas en MMIO: No uses operaciones atómicas como __sync_fetch_and_add o std::atomic en regiones de memoria asignadas a registros de dispositivos o búferes de memoria no almacenables en caché.

Cómo corregir bloqueos de bus de CPU

Para corregir los problemas de bloqueo del bus de la CPU, corrige la alineación de la memoria en el código fuente de la aplicación.

  • Evita usar #pragma pack o __attribute__((packed)) en estructuras que contengan variables atómicas, mutex o bloqueos de espera.
  • Usa directivas de alineación estándar (como alignas(64) en C++11 o __attribute__((aligned(64))) en C) para forzar la alineación de las variables que se usan mucho en operaciones atómicas con los límites de las líneas de caché.
  • Asegúrate de que no haya advertencias relacionadas con la alineación durante la compilación.
  • Asegúrate de usar solo mecanismos de bloqueo estándar (mutex, spinlocks) o instrucciones atómicas en RAM estándar que se pueda almacenar en caché, nunca en memoria MMIO o UC.

Si los pasos para solucionar el problema no lo resolvieron, comunícate con el servicio de Atención al cliente de Cloud y proporciona toda la información que recopilaste durante la solución de problemas.