Menghindari inversi prioritas

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

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

Latar belakang

Server audio AudioFlinger Android dan AudioTrack/AudioRecord implementasi klien sedang dirancang ulang untuk mengurangi latensi. Pekerjaan ini dimulai pada 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. paket Premium AI penting adalah menetapkan sumber daya CPU ke waktu yang kritis thread dengan kebijakan penjadwalan yang lebih dapat diprediksi. Penjadwalan yang andal memungkinkan ukuran dan jumlah buffer audio dikurangi saat diam menghindari {i>underruns<i} dan {i>overrun<i}.

Inversi prioritas

Inversi prioritas adalah mode kegagalan klasik dari sistem waktu nyata, saat tugas dengan prioritas lebih tinggi diblokir untuk waktu tunggu tanpa batas untuk tugas dengan prioritas lebih rendah guna melepaskan sumber daya seperti (bersama negara bagian yang dilindungi oleh) mutex.

Dalam sistem audio, inversi prioritas biasanya berwujud sebagai gangguan (klik, pop, cabut), audio berulang ketika buffer sirkular digunakan, atau penundaan dalam merespons suatu perintah.

Solusi umum untuk inversi prioritas adalah dengan meningkatkan ukuran buffer audio. Akan tetapi, metode ini meningkatkan latensi dan hanya menyembunyikan bukan menyelesaikannya. Lebih baik memahami dan mencegah prioritas inversi, seperti yang terlihat di bawah ini.

Dalam implementasi audio Android, inversi prioritas adalah mungkin terjadi di tempat tersebut. Jadi, Anda harus memfokuskan perhatian Anda di sini:

  • antara thread mixer normal dan thread mixer cepat di AudioFlinger
  • di antara thread callback aplikasi untuk AudioTrack yang cepat dan thread mixer cepat (keduanya memiliki prioritas yang lebih tinggi, tetapi sedikit prioritas yang berbeda)
  • di antara thread callback aplikasi untuk AudioRecord yang cepat dan thread pengambilan cepat (mirip dengan yang sebelumnya)
  • dalam implementasi Hardware Abstraksi Layer (HAL) audio, misalnya untuk telepon atau pengurangan gema
  • dalam {i>driver<i} audio di {i>kernel<i}
  • antara thread callback AudioTrack atau AudioRecord dan thread aplikasi lainnya (ini di luar kendali kami)

Solusi umum

Solusi umumnya meliputi:

  • menonaktifkan interupsi
  • mutex pewarisan prioritas

Menonaktifkan interupsi tidak mungkin dilakukan di ruang pengguna Linux, dan memang tidak berfungsi untuk Symmetric Multi-Processors (SMP).

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

Teknik yang digunakan oleh Android

Eksperimen dimulai dengan "coba kunci" dan kunci dengan waktu tunggu. Berikut adalah varian pemblokiran yang tidak bersifat memblokir dan terbatas dari kunci mutex operasi. Coba kunci dan kunci dengan waktu tunggu bekerja cukup baik, tapi rentan terhadap beberapa mode kegagalan yang tidak jelas: server tidak dijamin dapat mengakses status bersama jika klien kebetulan sedang sibuk, dan waktu tunggu kumulatifnya bisa terlalu panjang jika ada rangkaian panjang kunci tidak terkait yang semua waktu habis.

Kami juga menggunakan operasi atomik seperti:

  • penambahan
  • bitwise "atau"
  • bitwise "dan"

Semua ini mengembalikan nilai sebelumnya dan menyertakan Hambatan SMP. Kekurangannya adalah metode ini dapat memerlukan percobaan ulang yang tidak terikat. Dalam praktiknya, kami menemukan bahwa percobaan ulang bukanlah masalah.

Catatan: Operasi atomik dan interaksinya dengan batasan memori sangat disalahpahami dan digunakan secara tidak benar. Kami menyertakan metode ini di sini untuk kelengkapan tetapi sarankan Anda juga membaca artikel SMP Primer untuk Android untuk informasi lebih lanjut.

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

  • Gunakan penulis tunggal dengan pembaca tunggal yang tidak memblokir Antrean FIFO untuk data.
  • Coba salinan negara bagian, bukan bagikan status antara tinggi dan modul prioritas rendah.
  • Jika status perlu dibagikan, batasi status ke ukuran maksimum kata 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 FIFO penulis tunggal yang tidak memblokir antrean digunakan untuk status, bukan data, kecuali penulis menciutkan push yang berdekatan menjadi satu push.
  • Perhatikan hambat memori untuk ketepatan SMP.
  • Percayai, tetapi lakukan verifikasi. Saat berbagi negara bagian di antara proses, jangan mengasumsikan bahwa keadaan terbentuk dengan baik. Misalnya, periksa bahwa indeks berada dalam batas. Verifikasi ini tidak diperlukan di antara rangkaian pesan dalam proses yang sama, di antara proses kepercayaan bersama (yang biasanya memiliki UID yang sama). Juga tidak diperlukan untuk data seperti audio PCM yang kerusakan yang tidak penting.

Algoritma non-pemblokiran

Algoritma non-pemblokiran telah menjadi subjek dari banyak penelitian baru-baru ini. Namun dengan pengecualian antrean FIFO dengan satu pembaca tunggal, kami menemukan bahwa kode ini rumit dan rentan terhadap kesalahan.

Mulai Android 4.2, Anda dapat menemukan fungsi kelas pembaca/penulis tunggal di lokasi berikut:

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

Ini dirancang khusus untuk AudioFlinger dan tidak tujuan umum. Algoritma non-pemblokiran dikenal karena sulit untuk di-debug. Anda dapat melihat kode ini sebagai model. Tapi bersikaplah menyadari mungkin ada {i>bug<i}, dan kelas tidak dijamin akan cocok untuk tujuan lain.

Bagi developer, beberapa contoh kode aplikasi OpenSL ES harus diupdate menjadi menggunakan algoritma yang tidak memblokir atau mereferensikan library open source non-Android.

Kami telah memublikasikan contoh penerapan FIFO non-pemblokiran yang dirancang khusus untuk pada kode aplikasi Anda. Lihat file ini yang berada di direktori sumber platform frameworks/av/audio_utils:

Alat

Sepengetahuan kami, tidak ada alat otomatis untuk menemukan inversi prioritas, terutama sebelum itu terjadi. Agak besar alat analisis kode statis penelitian mampu menemukan prioritas jika dapat mengakses seluruh codebase. Tentu saja, jika kode pengguna arbitrer terlibat (seperti yang ada di sini untuk aplikasi) atau merupakan codebase besar (seperti untuk kernel Linux dan driver perangkat), analisis statis mungkin tidak praktis. Yang paling penting adalah baca kode dengan sangat hati-hati dan memahami seluruh bagian sistem dan interaksinya. Alat seperti systrace dan ps -t -p berguna untuk melihat inversi prioritas setelah terjadi, tetapi jangan tidak memberi tahu Anda sebelumnya.

Kata terakhir

Setelah semua diskusi ini, jangan takut mutex. mutex cocok untuk penggunaan biasa, jika digunakan dan diterapkan dengan benar dalam kasus penggunaan non-kritis biasa. Tapi antara tinggi dan pada tugas prioritas rendah dan dalam sistem yang sensitif terhadap waktu, {i>mutex<i} lebih berpotensi menimbulkan masalah.