Questo articolo spiega come il sistema audio di Android tenta di evitare la inversione di priorità e mette in evidenza le tecniche che puoi utilizzare anche tu.
Queste tecniche possono essere utili agli sviluppatori di app audio ad alte prestazioni, OEM e fornitori di SoC che stanno implementando un HAL audio. Tieni presente che l'implementazione di queste tecniche non garantisce la prevenzione di glitch o altri errori, in particolare se utilizzate al di fuori del contesto audio. I risultati possono variare e ti consigliamo di eseguire la tua valutazione e i tuoi test.
Sfondo
L'implementazione del client del server audio Android AudioFlinger e AudioTrack/AudioRecord è in fase di riprogettazione per ridurre la latenza. Questo lavoro è iniziato in Android 4.1 e ha continuato con ulteriori miglioramenti in 4.2, 4.3, 4.4 e 5.0.
Per ottenere questa latenza inferiore, sono state necessarie molte modifiche in tutto il sistema. Una modifica importante è l'assegnazione delle risorse della CPU ai thread critici per il tempo con un criterio di pianificazione più prevedibile. La pianificazione affidabile consente di ridurre le dimensioni e i conteggi dei buffer audio, evitando al contempo sottocarichi e sovraccarichi.
Inversione della priorità
L'inversione della priorità è un classico modo di errore dei sistemi in tempo reale, in cui un'attività con priorità più alta è bloccata per un periodo di tempo illimitato in attesa che un'attività con priorità inferiore rilasci una risorsa, ad esempio un mutex (stato condiviso protetto).
In un sistema audio, l'inversione della priorità si manifesta in genere come un glitch (clic, pop, interruzione), audio ripetuto quando vengono utilizzati buffer circolari o un ritardo nella risposta a un comando.
Una soluzione comune per l'inversione della priorità è aumentare le dimensioni del buffer audio. Tuttavia, questo metodo aumenta la latenza e nasconde il problema invece di risolverlo. È meglio comprendere e prevenire l'inversione della priorità, come mostrato di seguito.
Nell'implementazione audio di Android, l'inversione della priorità è più probabile che si verifichi in questi punti. Pertanto, dovresti concentrarti su questo:
- tra il thread del mixer normale e il thread del mixer veloce in AudioFlinger
- tra il thread di callback dell'applicazione per un AudioTrack veloce e il thread del mixer veloce (entrambi hanno una priorità elevata, ma leggermente diversa)
- tra il thread di callback dell'applicazione per un AudioRecord veloce e il thread di acquisizione veloce (simile al precedente)
- nell'implementazione dell'Hardware Abstraction Layer (HAL) audio, ad esempio per la telefonia o l'eliminazione dell'eco
- all'interno del driver audio nel kernel
- tra il thread di callback di AudioTrack o AudioRecord e altri thread dell'app (questo non è sotto il nostro controllo)
Soluzioni comuni
Le soluzioni tipiche includono:
- disattivare le interruzioni
- mutex di eredità della priorità
La disattivazione delle interruzioni non è fattibile nello spazio utente di Linux e non funziona per i sistemi SMP (Symmetric Multi-Processor).
I futex con ereditarietà della priorità (mutex nello spazio utente veloce) non vengono utilizzati nel sistema audio perché sono relativamente pesanti e perché si basano su un client attendibile.
Tecniche utilizzate da Android
Gli esperimenti sono iniziati con "try lock" e blocco con timeout. Si tratta di varianti non bloccanti e con blocco limitato dell'operazione di blocco mutex. I metodi try lock e lock with timeout hanno funzionato abbastanza bene, ma erano soggetti a un paio di modalità di errore oscure: non era garantito che il server potesse accedere allo stato condiviso se il client era occupato e il timeout cumulativo poteva essere troppo lungo se esisteva una lunga sequenza di blocchi non correlati che hanno tutti superato il timeout.
Utilizziamo anche operazioni atomiche come:
- aumenta
- "or" a livello di bit
- Esegui l'operazione "AND" a livello di bit
Tutti questi valori restituiscono il valore precedente e includono le barriere SMP necessarie. Lo svantaggio è che possono richiedere ripetuti tentativi. In pratica, abbiamo riscontrato che i tentativi di nuovo invio non sono un problema.
Nota:le operazioni atomiche e le relative interazioni con le barriere di memoria sono notoriamente poco comprese e utilizzate in modo errato. Abbiamo incluso questi metodi qui per completezza, ma ti consigliamo di leggere anche l'articolo SMP Primer per Android per ulteriori informazioni.
Abbiamo ancora e utilizziamo la maggior parte degli strumenti sopra indicati e di recente abbiamo aggiunto queste tecniche:
- Utilizza code FIFO non bloccanti con un solo lettore e un solo scrittore per i dati.
- Prova a copiare lo stato anziché condividere lo stato tra i moduli di priorità elevata e di priorità bassa.
- Quando lo stato deve essere condiviso, limitalo alla parola di dimensione massima a cui è possibile accedere in modo atomico in un'operazione con un bus senza ripetizioni.
- Per stati complessi con più parole, utilizza una coda di stato. Una coda di stato è in pratica solo una coda FIFO non bloccante con un solo lettore e un solo scrittore utilizzata per lo stato anziché per i dati, tranne per il fatto che lo scrittore comprime i push adiacenti in un unico push.
- Presta attenzione alle barriere di memoria per la correttezza dell'SMP.
- Fidati, ma verifica. Quando condividi stato tra i processi, non assumere che lo stato sia ben formato. Ad esempio, controlla che gli indici rientrano nei limiti. Questa verifica non è necessaria tra i thread nella stessa procedura, tra procedure di attendibilità reciproca (che tipicamente hanno lo stesso UID). Inoltre, non è necessario per i dati condivisi come l'audio PCM, in cui un danneggiamento non è rilevante.
Algoritmi non bloccanti
Gli algoritmi non bloccanti sono stati oggetto di molti studi recenti. Tuttavia, ad eccezione delle code FIFO con un solo lettore e un solo scrittore, abbiamo riscontrato che sono complesse e soggette a errori.
A partire da Android 4.2, puoi trovare le nostre classi non bloccanti con un solo lettore/scrittore in queste posizioni:
- frameworks/av/include/media/nbaio/
- frameworks/av/media/libnbaio/
- frameworks/av/services/audioflinger/StateQueue*
Sono stati progettati specificamente per AudioFlinger e non sono di uso generale. Gli algoritmi non bloccanti sono notoriamente difficili da eseguire il debug. Puoi considerare questo codice come un modello. Tuttavia, tieni conto che potrebbero esserci bug e che non è garantito che i corsi siano adatti per altri scopi.
Per gli sviluppatori, parte del codice dell'applicazione OpenSL ES di esempio deve essere aggiornato per utilizzare algoritmi non bloccanti o fare riferimento a una libreria open source non Android.
Abbiamo pubblicato un esempio di implementazione FIFO non bloccante progettata specificamente per il codice dell'applicazione. Consulta questi file nella directory di origine della piattaformaframeworks/av/audio_utils
:
Strumenti
Al meglio delle nostre conoscenze, non esistono strumenti automatici per trovare l'inversione della priorità, soprattutto prima che si verifichi. Alcuni strumenti di analisi del codice statico di ricerca sono in grado di trovare inversioni di priorità se sono in grado di accedere all'intera base di codice. Naturalmente, se è coinvolto codice utente arbitrario (come in questo caso per l'applicazione) o se si tratta di una base di codice di grandi dimensioni (come per il kernel di Linux e i driver di dispositivo), l'analisi statica potrebbe non essere praticabile. La cosa più importante è leggere il codice molto attentamente e avere una buona conoscenza dell'intero sistema e delle interazioni. Strumenti come
systrace
e
ps -t -p
sono utili per vedere l'inversione della priorità dopo che si verifica, ma
non ti avvisano in anticipo.
Un'ultima parola
Dopo tutta questa discussione, non aver paura dei mutex. I mutex sono utili per l'uso normale, se utilizzati e implementati correttamente in casi d'uso ordinari non critici in termini di tempo. Tuttavia, tra attività ad alta e bassa priorità e in sistemi sensibili al tempo, i mutex hanno maggiori probabilità di causare problemi.