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. Perhatikan bahwa penerapan teknik ini tidak dijamin dapat mencegah gangguan atau kegagalan lainnya, terutama jika digunakan di luar konteks audio. Hasil Anda mungkin berbeda, 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 diperlukan di seluruh sistem. Salah satu perubahan penting adalah menetapkan resource CPU ke thread yang penting untuk waktu 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 dari sistem real-time, dengan tugas berprioritas lebih tinggi diblokir untuk waktu yang tidak terbatas saat menunggu tugas berprioritas lebih rendah merilis resource seperti (status bersama yang dilindungi) mutex.

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

Solusi umum untuk inversi prioritas adalah 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 implementasi audio Android, inversi prioritas kemungkinan besar terjadi di tempat ini. Jadi, Anda harus memfokuskan perhatian 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 ditingkatkan, tetapi prioritasnya sedikit berbeda)
  • antara thread callback aplikasi untuk AudioRecord cepat dan thread pengambilan cepat (mirip dengan sebelumnya)
  • dalam implementasi Hardware Abstraction Layer (HAL) audio, misalnya untuk teleponi atau peredam gema
  • dalam driver audio di kernel
  • antara thread callback AudioTrack atau AudioRecord dan thread aplikasi lainnya (ini di luar kendali kami)

Solusi umum

Solusi umum mencakup:

  • menonaktifkan interupsi
  • mutex pewarisan prioritas

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

Futex pewarisan prioritas (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 kunci mutex yang tidak memblokir dan memblokir terbatas. Coba 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 sedang sibuk, dan waktu tunggu kumulatif dapat terlalu lama jika ada urutan panjang kunci yang tidak terkait yang semuanya habis waktu tunggunya.

Kami juga menggunakan operasi atomik seperti:

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

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

Catatan: Operasi atomik dan interaksinya dengan penghalang memori sangat sering disalahpahami dan digunakan dengan tidak benar. Kami menyertakan metode ini di sini untuk kelengkapan, tetapi sebaiknya Anda juga membaca artikel SMP Primer for 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 pembaca tunggal penulis tunggal yang tidak memblokir untuk data.
  • Coba salin status, bukan bagikan status antara modul prioritas tinggi dan rendah.
  • Jika status perlu dibagikan, batasi status ke kata ukuran maksimum yang dapat diakses secara atomik dalam satu operasi bus tanpa percobaan ulang.
  • Untuk status multi-kata yang kompleks, gunakan antrean status. Antrean status pada dasarnya hanyalah antrean FIFO pembaca tunggal penulis tunggal yang tidak memblokir yang digunakan untuk status, bukan data, kecuali penulis menggabungkan push yang berdekatan menjadi satu push.
  • Perhatikan penghalang memori untuk kebenaran SMP.
  • Percaya, tetapi tetap lakukan verifikasi. Saat berbagi status antarproses, jangan berasumsi bahwa statusnya terbentuk dengan baik. Misalnya, periksa apakah indeks berada dalam batas. Verifikasi ini tidak diperlukan antara thread dalam proses yang sama, antara proses yang saling tepercaya (yang biasanya memiliki UID yang sama). Verifikasi ini juga tidak diperlukan untuk data bersama seperti audio PCM yang kerusakannya tidak signifikan.

Algoritma yang tidak memblokir

Algoritma yang tidak memblokir telah menjadi subjek dari banyak studi terbaru. Namun, kecuali antrean FIFO pembaca tunggal penulis tunggal, kami menemukan bahwa algoritma ini kompleks dan rentan error.

Mulai Android 4.2, Anda dapat menemukan class pembaca/penulis tunggal yang tidak memblokir di lokasi berikut:

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

Class ini dirancang khusus untuk AudioFlinger dan bukan untuk tujuan umum. Algoritma yang tidak memblokir 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 kode aplikasi OpenSL ES contoh harus diupdate untuk menggunakan algoritma yang tidak memblokir atau mereferensikan library open source non-Android.

Kami telah memublikasikan contoh implementasi FIFO yang tidak memblokir 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 terjadi. Beberapa alat analisis kode statis penelitian 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. Hal terpenting adalah membaca kode dengan sangat cermat dan memahami seluruh sistem dan interaksinya. Alat seperti systrace dan ps -t -p berguna untuk melihat inversi prioritas setelah terjadi, tetapi tidak memberi tahu Anda sebelumnya.

Kata penutup

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 penting untuk waktu. Namun, antara tugas berprioritas tinggi dan rendah serta dalam sistem yang sensitif terhadap waktu, mutex lebih cenderung menyebabkan masalah.