Menghindari inversi prioritas

Artikel ini menjelaskan cara sistem audio Android mencoba menghindari inversi prioritas, dan menyoroti teknik yang juga dapat Anda gunakan.

Teknik ini mungkin berguna bagi developer aplikasi audio berperforma tinggi, OEM, dan penyedia SoC yang menerapkan HAL audio. Perlu diperhatikan bahwa penerapan teknik ini tidak dijamin dapat mencegah gangguan atau kegagalan lainnya, terutama jika digunakan di luar konteks audio. Hasil Anda mungkin bervariasi, dan Anda harus melakukan evaluasi dan pengujian sendiri.

Latar belakang

Server audio Android AudioFlinger dan implementasi klien AudioTrack/AudioRecord sedang dirancang ulang untuk mengurangi latensi. Pekerjaan ini dimulai di Android 4.1, dan dilanjutkan dengan peningkatan lebih lanjut di 4.2, 4.3, 4.4, dan 5.0.

Untuk mencapai latensi yang lebih rendah ini, banyak perubahan yang diperlukan di seluruh sistem. Satu perubahan penting adalah menetapkan resource CPU ke thread yang penting waktunya dengan kebijakan penjadwalan yang lebih dapat diprediksi. Penjadwalan yang andal memungkinkan ukuran dan jumlah buffer audio dikurangi sekaligus menghindari underrun dan overrun.

Inversi prioritas

Inversi prioritas adalah mode kegagalan klasik sistem real-time, dengan tugas berprioritas lebih tinggi diblokir selama waktu yang tidak terbatas menunggu tugas berprioritas lebih rendah untuk melepaskan resource seperti (status bersama yang dilindungi oleh) mutex.

Dalam sistem audio, inversi prioritas biasanya muncul sebagai gangguan (klik, pop, dropout), audio berulang saat buffer melingkar digunakan, atau penundaan dalam merespons perintah.

Solusi umum untuk inversi prioritas adalah dengan meningkatkan ukuran buffer audio. Namun, metode ini meningkatkan latensi dan hanya menyembunyikan masalah, bukan menyelesaikannya. Sebaiknya pahami dan cegah inversi prioritas, seperti yang terlihat di bawah.

Dalam penerapan audio Android, inversi prioritas kemungkinan terbesar terjadi di tempat ini. Jadi, Anda harus memfokuskan perhatian Anda di sini:

  • antara thread mixer normal dan thread mixer cepat di AudioFlinger
  • antara thread callback aplikasi untuk thread AudioTrack cepat dan mixer cepat (keduanya memiliki prioritas yang lebih tinggi, tetapi prioritasnya sedikit berbeda)
  • antara thread callback aplikasi untuk AudioRecord yang cepat dan thread perekaman yang cepat (mirip dengan sebelumnya)
  • dalam implementasi Hardware Abstraction Layer (HAL) audio, misalnya untuk telepon atau peredam gema
  • dalam driver audio di kernel
  • antara thread callback AudioTrack atau AudioRecord dan thread aplikasi lainnya (hal ini di luar kendali kita)

Solusi umum

Solusi umumnya meliputi:

  • menonaktifkan gangguan
  • mutex pewarisan prioritas

Menonaktifkan interupsi tidak dapat dilakukan di ruang pengguna Linux, dan tidak berfungsi untuk Multi-Processor Simetris (SMP).

Pewarisan prioritas futex (mutex ruang pengguna cepat) tidak digunakan dalam sistem audio karena relatif berat, dan karena bergantung pada klien tepercaya.

Teknik yang digunakan oleh Android

Eksperimen dimulai dengan "try lock" dan kunci dengan waktu tunggu. Ini adalah varian pemblokiran non-pemblokiran dan pemblokiran terbatas dari operasi kunci mutex. Mencoba kunci dan kunci dengan waktu tunggu berfungsi cukup baik, tetapi rentan terhadap beberapa mode kegagalan yang tidak jelas: server tidak dijamin dapat mengakses status bersama jika klien kebetulan sedang sibuk, dan waktu tunggu kumulatif dapat terlalu lama jika ada urutan panjang kunci yang tidak terkait yang semuanya habis waktu tunggunya.

Kita juga menggunakan operasi atomik seperti:

  • penambahan
  • "atau" bitwise
  • bitwise "and"

Semua ini menampilkan nilai sebelumnya dan menyertakan penghalang SMP yang diperlukan. Kelemahannya adalah mereka dapat memerlukan percobaan ulang yang tidak terbatas. Dalam praktiknya, kami mendapati bahwa percobaan ulang tidak menjadi masalah.

Catatan: Operasi atomik dan interaksinya dengan pembatas memori sangat sering disalahpahami dan digunakan secara salah. Kami menyertakan metode ini di sini untuk kelengkapan, tetapi sebaiknya Anda juga membaca artikel Primer SMP untuk Android untuk informasi lebih lanjut.

Kami masih memiliki dan menggunakan sebagian besar alat di atas, dan baru-baru ini menambahkan teknik berikut:

  • Gunakan antrean FIFO satu pembaca satu penulis yang tidak memblokir untuk data.
  • Coba salin status, bukan bagikan status antara modul dengan prioritas tinggi dan rendah.
  • Jika status perlu dibagikan, batasi status ke kata ukuran maksimum yang dapat diakses secara atomik dalam operasi satu bus tanpa percobaan ulang.
  • Untuk status multi-kata yang kompleks, gunakan antrean status. Antrean status pada dasarnya hanyalah antrean FIFO satu pembaca satu penulis non-blocking yang digunakan untuk status, bukan data, kecuali penulis menggabungkan push yang berdekatan menjadi satu push.
  • Perhatikan pembatasan memori untuk akurasi SMP.
  • Percaya, tetapi verifikasi. Saat berbagi status antar-proses, jangan asumsikan bahwa status tersebut terbentuk dengan baik. Misalnya, pastikan indeks berada dalam batas. Verifikasi ini tidak diperlukan di antara thread dalam proses yang sama, di antara proses saling percaya (yang biasanya memiliki UID yang sama). Hal ini juga tidak diperlukan untuk data bersama seperti audio PCM yang kerusakannya tidak penting.

Algoritma non-pemblokiran

Algoritma non-pemblokiran telah menjadi subjek banyak studi terbaru. Namun, dengan pengecualian antrean FIFO satu pembaca satu penulis, kami mendapati bahwa antrean tersebut kompleks dan rentan error.

Mulai Android 4.2, Anda dapat menemukan class pembaca/penulis tunggal tanpa pemblokiran di lokasi berikut:

  • frameworks/av/include/media/nbaio/
  • frameworks/av/media/libnbaio/
  • frameworks/av/services/audioflinger/StateQueue*

Ini dirancang khusus untuk AudioFlinger dan tidak bersifat umum. Algoritma non-pemblokiran terkenal sulit di-debug. Anda dapat melihat kode ini sebagai model. Namun, perlu diketahui bahwa mungkin ada bug, dan class tidak dijamin cocok untuk tujuan lain.

Untuk developer, beberapa contoh kode aplikasi OpenSL ES harus diupdate untuk menggunakan algoritma non-pemblokiran atau mereferensikan library open source non-Android.

Kami telah memublikasikan contoh implementasi FIFO non-blocking yang dirancang khusus untuk kode aplikasi. Lihat file ini yang terletak di direktori sumber platform frameworks/av/audio_utils:

Alat

Sejauh yang kami ketahui, tidak ada alat otomatis untuk menemukan inversi prioritas, terutama sebelum hal itu terjadi. Beberapa alat analisis kode statis riset dapat menemukan inversi prioritas jika dapat mengakses seluruh codebase. Tentu saja, jika kode pengguna arbitrer terlibat (seperti di sini untuk aplikasi) atau merupakan codebase besar (seperti untuk kernel Linux dan driver perangkat), analisis statis mungkin tidak praktis. Yang terpenting adalah membaca kode dengan sangat cermat dan memahami seluruh sistem dan interaksi dengan baik. Alat seperti systrace dan ps -t -p berguna untuk melihat inversi prioritas setelah terjadi, tetapi tidak memberi tahu Anda sebelumnya.

Kata terakhir

Setelah semua diskusi ini, jangan takut dengan mutex. Mutex adalah teman Anda untuk penggunaan biasa, jika digunakan dan diterapkan dengan benar dalam kasus penggunaan biasa yang tidak mendesak. Namun, antara tugas berprioritas tinggi dan berprioritas rendah serta dalam sistem yang sensitif terhadap waktu, mutex lebih mungkin menyebabkan masalah.