Menghindari inversi prioritas

Artikel ini menjelaskan cara sistem audio Android berupaya 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 diubah arsitekturnya 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. Salah 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, di mana tugas berprioritas lebih tinggi diblokir selama waktu yang tidak terbatas saat menunggu tugas berprioritas lebih rendah melepaskan resource seperti (status bersama yang dilindungi oleh) mutex.

Dalam sistem audio, inversi prioritas biasanya muncul sebagai glitch (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. Lebih baik memahami dan mencegah inversi prioritas, seperti yang terlihat di bawah.

Dalam penerapan audio Android, inversi prioritas kemungkinan besar terjadi di tempat-tempat berikut. Jadi, Anda harus memfokuskan perhatian Anda di sini:

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

Solusi umum

Solusi umum meliputi:

  • menonaktifkan interupsi
  • mutex pewarisan prioritas

Menonaktifkan interupsi tidak dapat dilakukan di ruang pengguna Linux, dan tidak berfungsi untuk Symmetric Multi-Processors (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 "coba kunci" dan kunci dengan waktu tunggu. Ini adalah varian operasi penguncian mutex yang tidak memblokir dan memblokir terbatas. Try lock dan lock dengan waktu tunggu berfungsi cukup baik, tetapi rentan terhadap beberapa mode kegagalan yang tidak jelas: server tidak dijamin dapat mengakses status bersama jika klien sedang sibuk, dan waktu tunggu kumulatif bisa terlalu lama jika ada urutan panjang kunci yang tidak terkait yang semuanya habis waktunya.

Kami juga menggunakan operasi atomik seperti:

  • penambahan
  • bitwise "or"
  • bitwise "and"

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

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

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

  • Gunakan antrean FIFO single-reader single-writer non-blocking untuk data.
  • Coba salin status, bukan bagikan status antara modul berprioritas tinggi dan rendah.
  • Jika status perlu dibagikan, batasi status ke kata berukuran 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 single-reader single-writer non-blocking yang digunakan untuk status, bukan data, kecuali penulis menggabungkan push yang berdekatan menjadi satu push.
  • Perhatikan memory barrier untuk kebenaran SMP.
  • Percayai, tetapi verifikasi. Saat membagikan status antarproses, jangan menganggap bahwa statusnya sudah terbentuk dengan baik. Misalnya, periksa apakah indeks berada dalam batas. Verifikasi ini tidak diperlukan di antara thread dalam proses yang sama, di antara proses yang saling tepercaya (yang biasanya memiliki UID yang sama). Hal ini juga tidak diperlukan untuk data bersama seperti audio PCM yang kerusakannya tidak signifikan.

Algoritma yang tidak memblokir

Algoritma non-blocking telah menjadi subjek banyak studi baru-baru ini. Namun, dengan pengecualian antrean FIFO penulis tunggal pembaca tunggal, kami mendapati bahwa antrean tersebut rumit dan rentan terhadap error.

Mulai Android 4.2, Anda dapat menemukan class penulis/pembaca tunggal non-blocking kami di lokasi berikut:

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

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

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

Kami telah memublikasikan contoh penerapan FIFO non-blocking yang dirancang khusus untuk kode aplikasi. Lihat file ini yang berada 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 penelitian dapat menemukan inversi prioritas jika dapat mengakses seluruh codebase. Tentu saja, jika kode pengguna arbitrer terlibat (seperti yang terjadi 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 interaksinya 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 pembahasan ini, jangan takut dengan mutex. Mutex adalah teman Anda untuk penggunaan biasa, jika digunakan dan diterapkan dengan benar dalam kasus penggunaan biasa yang tidak kritis terhadap waktu. Namun, di antara tugas berprioritas tinggi dan rendah, serta dalam sistem yang sensitif terhadap waktu, mutex lebih cenderung menyebabkan masalah.