Memecahkan masalah penguncian bus CPU

Dokumen ini menjelaskan cara mengidentifikasi dan memecahkan masalah penguncian bus CPU di sistem operasi tamu Linux. Dokumen ini membahas gejala penguncian bus CPU, cara mendiagnosis masalah ini menggunakan pesan log kernel, cara menemukan kode yang salah, dan cara menerapkan mitigasi atau perbaikan.

Ringkasan

Penguncian bus CPU terjadi saat prosesor harus menegaskan sinyal hardware LOCK# untuk mendapatkan akses eksklusif ke bus memori di seluruh sistem. Masalah ini biasanya terjadi dalam salah satu keadaan berikut:

  • Instruksi atomik beroperasi pada memori yang tidak selaras yang melintasi batas baris cache (kunci terpisah).
  • Instruksi atomik beroperasi pada memori yang ditetapkan sebagai uncacheable (UC), seperti Memory-Mapped I/O (MMIO).

Karena CPU menegaskan penguncian bus global, semua prosesor dan perangkat lain di sistem operasi tamu harus menunggu saat operasi memori selesai. Tingkat penguncian bus yang tinggi dapat menurunkan performa CPU secara signifikan.

Meskipun prosesor lama tidak melacak penguncian bus, prosesor x86 modern seperti Intel Sapphire Rapids dan yang lebih baru, atau AMD Zen 5 atau yang lebih baru, menyertakan fitur hardware yang mendeteksi penguncian bus CPU. Saat sebuah instruksi memicu penguncian bus CPU, CPU akan segera mengeluarkan pengecualian debug (#DB) setelah instruksi selesai.

Mulai dari kernel Linux versi 5.13 di Intel dan 6.13 di AMD, kernel Linux mencegat pengecualian #DB ini dan menerapkan mitigasi, biasanya dengan membatasi kecepatan proses yang salah. Dengan sengaja memaksa thread untuk tidur, kernel mencegah satu aplikasi memenuhi bus memori, sehingga menjaga performa sistem untuk instance komputasi lainnya dengan mengorbankan performa aplikasi yang bermasalah.

Gejala

Jika proses di guest Linux Anda memicu penguncian bus CPU, Anda mungkin mengalami gejala berikut:

  • Performa aplikasi yang menurun: Penguncian bus CPU dapat menyebabkan latensi yang tidak terduga untuk aplikasi.
  • Lonjakan beban di seluruh sistem: responsivitas sistem secara keseluruhan mungkin menurun.
  • Aplikasi tiba-tiba error: jika Anda mengonfigurasi kernel untuk menangani split atau bus lock secara ketat (split_lock_detect=fatal), maka aplikasi yang bermasalah dapat error dengan error SIGBUS.

Mengidentifikasi penguncian bus CPU

Untuk mengidentifikasi apakah instance komputasi Anda mengalami penguncian bus CPU, lakukan salah satu hal berikut:

  • Jika Anda telah mengaktifkan logging output port serial untuk instance komputasi, tinjau output port serial untuk mengetahui jejak penguncian bus CPU.
  • Tinjau log sistem operasi instance komputasi Anda (/var/log/messages) untuk melihat rekaman aktivitas penguncian bus CPU.

Contoh rekaman aktivitas penguncian bus CPU

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

Untuk mendeteksi penguncian bus CPU di masa mendatang, lakukan hal berikut:

  1. Aktifkan logging output port serial.
  2. Buat kebijakan pemberitahuan berbasis log untuk log berikut:

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

    Entri log ini memberi Anda nama proses (<process_name>) dan ID Proses (<pid>) yang bertanggung jawab atas penguncian bus CPU, serta alamat penunjuk instruksi tempat kesalahan terjadi.

Memecahkan masalah penguncian bus CPU

Jika Anda sedang mengembangkan atau mengompilasi aplikasi yang rusak, Anda dapat menggunakan peringatan compiler C atau C++ tertentu untuk mengidentifikasi variabel dan struktur yang dapat menyebabkan split lock.

Peringatan compiler

Jika Anda menggunakan GCC atau Clang, kompilasi kode Anda dengan flag berikut untuk membantu mengidentifikasi masalah perataan:

  • -Wcast-align atau -Wcast-align=strict: tanda ini memperingatkan Anda saat transmisi pointer meningkatkan perataan target yang diperlukan. Mentransmisikan buffer char* generik ke uint64_t* dan melakukan operasi atomik di dalamnya adalah penyebab klasik pemisahan kunci.
  • -Waddress-of-packed-member: tanda ini memperingatkan Anda saat Anda mengambil alamat anggota struct yang dipadatkan (misalnya, menggunakan #pragma pack(1) atau __attribute__((packed))). Karena struktur yang dipadatkan mengabaikan penyusunan memori alami, setiap operasi atomik pada anggota struct yang dipadatkan memiliki kemungkinan besar melintasi batas baris cache 64 byte.

Mendeteksi kunci memori yang tidak dapat di-cache (UC)

Jika operasi atomik pada memori yang tidak dapat di-cache menyebabkan penguncian bus CPU, peringatan kompiler tidak akan mendeteksinya. Masalah ini biasanya terjadi saat berinteraksi dengan memori perangkat:

  • Audit pemetaan memori: tinjau kode Anda untuk penggunaan mmap dengan flag seperti O_SYNC atau akses langsung ke /dev/mem atau /dev/uio.
  • Hindari operasi atomik pada MMIO: jangan gunakan operasi atomik seperti __sync_fetch_and_add atau std::atomic pada region memori yang dipetakan ke register perangkat atau buffer memori yang tidak dapat di-cache

Memperbaiki penguncian bus CPU

Anda dapat memperbaiki masalah penguncian bus CPU dengan mengoreksi perataan memori dalam kode sumber aplikasi.

  • Hindari penggunaan #pragma pack atau __attribute__((packed)) pada struktur yang berisi variabel atomik, mutex, atau spinlock.
  • Gunakan direktif perataan standar (seperti alignas(64) di C++11 atau __attribute__((aligned(64))) di C) untuk memaksa variabel yang banyak digunakan dalam operasi atomic agar diratakan ke batas baris cache.
  • Pastikan tidak ada peringatan terkait perataan selama kompilasi.
  • Pastikan Anda hanya menggunakan mekanisme penguncian standar (mutex, spinlock) atau instruksi atomik pada RAM standar yang dapat di-cache, bukan pada memori MMIO atau UC.

Jika langkah-langkah pemecahan masalah tidak menyelesaikan masalah, hubungi Cloud Customer Care dan sertakan semua informasi yang Anda kumpulkan selama pemecahan masalah.