Evita l'inversione delle priorità

In questo articolo viene spiegato in che modo il sistema audio Android tenta di evitare l'inversione delle priorità, ed evidenzia le tecniche che puoi utilizzare.

Queste tecniche possono essere utili agli sviluppatori di applicazioni app audio, OEM e fornitori di SoC che implementano HAL L'implementazione di queste tecniche non è la garanzia di prevenire glitch o altri guasti, in particolare utilizzati al di fuori del contesto audio. I risultati possono variare e dovresti condurre in prima persona la valutazione e i test.

Premessa

Server audio Android AudioFlinger e AudioTrack/AudioRecord dell'implementazione del client vengono riprogettati per ridurre la latenza. Questo lavoro è iniziato in Android 4.1 e ha continuato con ulteriori miglioramenti nelle versioni 4.2, 4.3, 4.4 e 5.0.

Per ottenere questa latenza più bassa, sono state necessarie molte modifiche in tutto il sistema. Uno. un cambiamento importante consiste nell'assegnare risorse della CPU a operazioni i thread con un criterio di pianificazione più prevedibile. Pianificazione affidabile consente di ridurre le dimensioni e i conteggi del buffer audio quando evitando incoerenze e sforamenti.

Inversione delle priorità

Inversione delle priorità è una classica modalità di errore dei sistemi in tempo reale, in cui un'attività con priorità più alta è bloccata per un tempo illimitato di attesa per un'attività a priorità più bassa per rilasciare una risorsa come (condiviso stato protetto da) un mutex.

In un sistema audio, l'inversione della priorità solitamente si manifesta come glitch (clic, pop, dropout), audio ripetuto quando il buffer circolare o ritardi nella risposta a un comando.

Una soluzione alternativa comune per l'inversione di priorità è aumentare le dimensioni del buffer audio. Tuttavia, questo metodo aumenta la latenza e si limita a nascondere il problema anziché risolverlo. È meglio comprendere e prevenire la priorità come mostrato di seguito.

Nell'implementazione audio su Android, l'inversione di priorità è la che si verificano in questi luoghi. Occorre quindi concentrare l'attenzione su questo aspetto:

  • tra il thread normale del mixer e il thread del mixer veloce in AudioFlinger
  • tra thread di callback dell'applicazione per un AudioTrack veloce e thread mixer veloce (entrambi hanno una priorità elevata, ma priorità diverse)
  • tra thread di callback dell'applicazione per un AudioRecord veloce e thread di acquisizione rapida (simile al precedente)
  • nell'implementazione dell'HAL (Hardware Abstraction Layer) audio, ad esempio per telefonia o cancellazione dell'eco
  • all'interno del driver audio
  • tra il thread di callback AudioTrack o AudioRecord e altri thread dell'app (questo è fuori dal nostro controllo)

Soluzioni comuni

Le soluzioni tipiche includono:

  • disabilitazione delle interruzioni
  • mutex di ereditarietà della priorità

La disattivazione degli interrupt non è fattibile nello spazio utente Linux e non funzionano per gli SMP (simmetrico multi-processo).

Eredità della priorità futexe (mutazioni veloci nello spazio utente) non vengono utilizzati nel sistema audio perché sono relativamente pesanti, e perché si affidano a un cliente affidabile.

Tecniche utilizzate da Android

Gli esperimenti sono iniziati con "trylock" e bloccalo con il timeout. Si tratta di varianti del blocco mutex non bloccanti e limitate operativa. Le funzionalità di blocco e blocco con timeout hanno funzionato abbastanza bene, ma suscettibili di un paio di modalità di errore oscurate: non è garantito che il server sia in grado di accedere allo stato condiviso se il client era occupato e il timeout cumulativo essere troppo lunga nel caso di una lunga sequenza di blocchi non correlati che sono scaduti.

Usiamo anche operazioni atomiche quali:

  • aumenta
  • "o" a livello di bit
  • "e" a livello di bit

Tutte queste risposte restituiscono il valore precedente e includono i necessari Barriere SMP. Lo svantaggio è che possono richiedere un numero illimitato di nuovi tentativi. Nella pratica, abbiamo riscontrato che i nuovi tentativi non sono un problema.

Nota: operazioni atomiche e loro interazioni con le barriere di memoria notoriamente mal comprese e utilizzate in modo scorretto. Includiamo questi metodi qui per completezza, ma ti consiglio di leggere anche l'articolo SMP Primer per Android per ulteriori informazioni.

Abbiamo ancora e usiamo la maggior parte degli strumenti di cui sopra e di recente abbiamo aggiunto queste tecniche:

  • Utilizzare un singolo scrittore con un singolo lettore che non blocca le pagine Coda FIFO per i dati.
  • Prova a copia anziché su uno stato condividi tra alto e basso a bassa priorità.
  • Quando è necessario condividere lo stato, limitalo ai dimensione massima parola a cui è possibile accedere a livello atomico con un singolo bus senza nuovi tentativi.
  • Per stati complessi con più parole, utilizza una coda di stati. Una coda di stati. è un file FIFO con un singolo lettore che non blocca le pagine utilizzata per lo stato anziché per i dati, ad eccezione del fatto che l'autore della scrittura comprime spinte adiacenti in un'unica spinta.
  • Fai attenzione a barriere di memoria per la correttezza SMP.
  • Fidati, ma verifica. Durante la condivisione stato tra i processi, non presupporre che lo stato sia in un formato corretto. Ad esempio, controlla che gli indici entro limiti. Questa verifica non è necessaria tra i thread nello stesso processo, tra processi di affidabilità reciproca (che in genere hanno lo stesso UID). Inoltre, non è necessario per i dati come i contenuti audio PCM, in cui la corruzione è irrilevante.

Algoritmi che non bloccano il processo

Algoritmi che non bloccano sono stati oggetto di studi recenti. Ma ad eccezione delle code FIFO per singolo lettore, li abbiamo trovati complessi e soggetti a errori.

A partire da Android 4.2, potete trovare i nostri strumenti corsi per singoli lettori/autori nelle seguenti località:

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

Sono state progettate specificamente per AudioFlinger e non sono per uso generico. Gli algoritmi che non bloccano sono noti per essere difficili da eseguire. Puoi visualizzare questo codice come modello. Ma sii a conoscenza di eventuali bug e non è garantito che i corsi siano adatto ad altri scopi.

Per gli sviluppatori, parte del codice dell'applicazione OpenSL ES di esempio dovrebbe essere aggiornato in Usare algoritmi che non bloccano o fare riferimento a una libreria open source non Android.

Abbiamo pubblicato un esempio di implementazione FIFO non bloccante, progettata specificatamente per il codice dell'applicazione. Visualizza questi file che si trovano nella directory di origine della piattaforma frameworks/av/audio_utils:

Strumenti

In base alle nostre conoscenze, non esistono strumenti automatici per l'inversione delle priorità, soprattutto prima che avvenga. Alcune gli strumenti di analisi del codice statico di ricerca sono in grado di dare priorità inversioni di posizione se è in grado di accedere all'intero codebase. Ovviamente, se è coinvolto un codice utente arbitrario (come è qui per l'applicazione) o è un codebase di grandi dimensioni (come il kernel Linux e i driver di dispositivo), l'analisi statica potrebbe non essere pratica. La cosa più importante è leggi molto attentamente il codice e acquisisci una buona conoscenza dell'intero il sistema e le interazioni. Strumenti come systrace e ps -t -p sono utili per vedere l'inversione della priorità dopo che si verifica, ma non te lo dico in anticipo.

Un'ultima parola

Dopo tutta questa discussione, non temere i mutex. Silenziose utili per il normale utilizzo, se utilizzati e implementati correttamente nei casi d'uso ordinari e non urgenti. Ma tra livelli alti e le attività a bassa priorità e nei sistemi sensibili al tempo, i mutex sono più che potrebbero causare problemi.