Este artigo explica como o sistema de áudio do Android tenta evitar inversão de prioridades, e destaca técnicas que você também pode usar.
Essas técnicas podem ser úteis para desenvolvedores de aplicativos apps de áudio, OEMs e provedores de SoC que implementam uma interface HAL A implementação dessas técnicas não é falhas ou outras falhas, especialmente se usadas fora do contexto de áudio. Os resultados podem variar, e você deve conduzir o seu próprio avaliação e testes.
Contexto
O servidor de áudio do Android AudioFlinger e o AudioTrack/AudioRecord estão sendo reprojetadas para reduzir a latência. Esse trabalho começou no Android 4.1 e continuou com outras melhorias em 4.2, 4.3, 4.4 e 5.0.
Para alcançar essa menor latência, muitas alterações foram necessárias em todo o sistema. Uma mais importante é atribuir recursos de CPU a recursos com uma política de programação mais previsível. Programação confiável permite reduzir o tamanho e a contagem do buffer de áudio para evitar underruns e overruns.
Inversão de prioridade
Inversão de prioridades é um modo de falha clássico dos sistemas em tempo real, em que uma tarefa de prioridade mais alta fica bloqueada por um tempo ilimitado de espera para uma tarefa de menor prioridade liberar um recurso como protegido por) um mutex.
Em um sistema de áudio, a inversão de prioridade normalmente se manifesta como uma falha (clicar, abrir, dropout), áudio repetido quando buffers circulares ou demora na resposta a um comando.
Uma solução alternativa comum para a inversão de prioridades é aumentar o tamanho do buffer de áudio. No entanto, esse método aumenta a latência e apenas esconde o problema em vez de resolvê-los. É melhor entender e evitar as prioridades de inversão da computação em nuvem, como mostrado abaixo.
Na implementação de áudio do Android, a inversão de prioridade é mais provavelmente ocorrerão nesses locais. Por isso, você deve concentrar sua atenção aqui:
- entre a linha de execução normal do mixer e a linha de execução rápida do mixer no AudioFlinger
- entre a sequência de retorno de chamada do aplicativo para uma AudioTrack rápida e linha de processamento rápida do mixer (ambos têm prioridade elevada, mas ligeiramente prioridades diferentes)
- entre o thread de callback do aplicativo para um AudioRecord rápido e o linha de execução de captura rápida (semelhante à anterior)
- na implementação de HAL (Camada de abstração de hardware) de áudio, por exemplo, para cancelamento de eco ou telefonia
- no driver de áudio no kernel
- entre a sequência de retorno do AudioTrack ou AudioRecord e outras linhas de execução do aplicativo (isso está fora do nosso controle)
Soluções comuns
As soluções típicas incluem:
- desativar interrupções
- exclusões múltiplas de herança prioritária
Não é possível desativar as interrupções no espaço do usuário do Linux não funciona com multiprocessadores simétricos (SMP, na sigla em inglês).
Herança de prioridade futexes (link em inglês) (mutexs múltiplas rápidas para o espaço do usuário) não são usadas no sistema de áudio porque são relativamente pesadas, e porque eles contam com um cliente confiável.
Técnicas usadas pelo Android
Experimentos iniciados com "try lock" e bloquear com tempo limite. São variantes não bloqueadoras e de bloqueio limitadas do bloqueio de mutex. operação As tentativas de bloqueio e bloqueio com tempo limite funcionaram bem, mas foram suscetíveis a alguns modos de falha obscuros: não havia garantia de acesso ao estado compartilhado o cliente estava ocupado, e o tempo limite acumulado poderia ser muito longo se houvesse uma sequência longa de bloqueios não relacionados que expirou.
Também usamos operações atômicas como:
- aumentar
- "ou" bit a bit
- "e" bit a bit
Todos eles retornam o valor anterior e incluem os valores barreiras de SMP. A desvantagem é que elas podem exigir novas tentativas ilimitadas. Na prática, descobrimos que as novas tentativas não são um problema.
Observação:operações atômicas e as interações delas com barreiras de memória que são claramente mal compreendidas e usadas incorretamente. Incluímos esses métodos aqui para fins de abrangência, mas recomendamos que você também leia o artigo SMP Primer para Android para mais informações.
Ainda temos e usamos a maioria das ferramentas acima e recentemente adicionou estas técnicas:
- Usar um único gravador sem bloqueios Filas FIFO para dados.
- Tente copiar o estado, em vez compartilhar entre alto e módulos de baixa prioridade.
- Quando o estado precisar ser compartilhado, limite-o ao tamanho máximo palavra que podem ser acessados atomicamente em operações com um barramento sem novas tentativas.
- Para estados complexos com várias palavras, use uma fila de estado. Uma fila de estado é basicamente um FIFO, de um único leitor e sem bloqueios, fila usada para o estado em vez de dados, mas o gravador se recolhe de ações adjacentes em um único push.
- Preste atenção barreiras de memória para a correção do SMP.
- Confie, mas verifique. Ao compartilhar estado entre processos, não supor que o estado está bem formado. Por exemplo, verifique se os índices que estão dentro dos limites. Essa verificação não é necessária entre conversas no mesmo processo, entre processos de confiança mútua (que costumam ter o mesmo UID). Também é desnecessário para arquivos Dados como áudio PCM, em que a corrupção não tem importância.
Algoritmos sem bloqueio
Algoritmos sem bloqueio foram alvo de muitos estudos recentes. Mas, com exceção das filas FIFO de um único leitor de leitor único, consideramos complexos e suscetíveis a erros.
A partir do Android 4.2, você encontra nossos modelos classes de leitor único/escritor nestes locais:
- frameworks/av/include/media/nbaio/
- frameworks/av/media/libnbaio/
- frameworks/av/services/audioflinger/StateQueue*
Eles foram projetados especificamente para o AudioFlinger e não são de uso geral. Algoritmos sem bloqueio são conhecidos por serem difíceis de depurar. Você pode analisar esse código como um modelo. Mas tenha conscientes de bugs e não há garantia de que as aulas serão adequado para outros fins.
Para desenvolvedores, alguns dos exemplos de código do aplicativo OpenSL ES devem ser atualizados para usar algoritmos sem bloqueio ou fazer referência a uma biblioteca de código aberto que não seja Android.
Publicamos um exemplo de implementação do FIFO sem bloqueio projetada especificamente para
no código do aplicativo. Confira esses arquivos localizados no diretório de origem da plataforma
frameworks/av/audio_utils
:
- include/audio_utils/fifo.h (link em inglês)
- fifo.c (link em inglês)
- include/audio_utils/roundup.h (link em inglês)
- roundup.c (link em inglês)
Ferramentas
Até onde sabemos, não há ferramentas automáticas para
encontrando a inversão de prioridades, especialmente antes que aconteça. Algumas
pesquisar ferramentas de análise de código estático são capazes de encontrar prioridade
as inversões, se for possível acessar toda a base de código. Claro, se
código de usuário arbitrário está envolvido (como está aqui para o aplicativo)
ou uma grande base de código (para o kernel do Linux e os drivers de dispositivo),
a análise estática pode ser impraticável. O mais importante é
leia o código com muita atenção e tenha uma boa noção
do sistema e das interações. Ferramentas como
Systrace;
e
ps -t -p
são úteis para ver a inversão de prioridade depois que ela ocorre, mas
não lhe dizer com antecedência.
Uma última palavra
Depois de toda essa discussão, não tenha medo de mutexes. Muções múltiplas são seus amigos para o uso cotidiano, quando usados e implementados corretamente em casos de uso comuns e não urgentes. Mas entre alta e tarefas de baixa prioridade e em sistemas sensíveis ao tempo, as exclusões múltiplas são mais que podem causar problemas.