O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.
Esta página foi traduzida pela API Cloud Translation.
Switch to English

Evitando Inversão de Prioridade

Este artigo explica como o sistema de áudio do Android tenta evitar a inversão de prioridade e destaca técnicas que você também pode usar.

Essas técnicas podem ser úteis para desenvolvedores de aplicativos de áudio de alto desempenho, OEMs e provedores de SoC que estão implementando um HAL de áudio. Observe que a implementação dessas técnicas não garante a prevenção de glitches ou outras falhas, principalmente se usadas fora do contexto de áudio. Seus resultados podem variar e você deve conduzir sua própria avaliação e teste.

fundo

O servidor de áudio Android AudioFlinger e a implementação do cliente AudioTrack / AudioRecord estão sendo re-arquitetados para reduzir a latência. Este trabalho começou no Android 4.1 e continuou com mais melhorias em 4.2, 4.3, 4.4 e 5.0.

Para atingir essa latência mais baixa, muitas alterações foram necessárias em todo o sistema. Uma mudança importante é atribuir recursos de CPU a threads de tempo crítico com uma política de agendamento mais previsível. A programação confiável permite que os tamanhos e contagens do buffer de áudio sejam reduzidos, ao mesmo tempo em que evita underruns e overruns.

Inversão de prioridade

A inversão de prioridade é um modo de falha clássico de sistemas em tempo real, onde uma tarefa de maior prioridade é bloqueada por um tempo ilimitado esperando por uma tarefa de menor prioridade para liberar um recurso como (estado compartilhado protegido por) um mutex .

Em um sistema de áudio, a inversão de prioridade normalmente se manifesta como uma falha (clique, pop, queda), áudio repetido quando buffers circulares são usados ​​ou atraso na resposta a um comando.

Uma solução alternativa comum para a inversão de prioridade é aumentar os tamanhos do buffer de áudio. No entanto, esse método aumenta a latência e apenas oculta o problema em vez de resolvê-lo. É melhor entender e prevenir a inversão de prioridades, conforme visto a seguir.

Na implementação de áudio do Android, é mais provável que ocorra inversão de prioridade nesses locais. E então você deve focar sua atenção aqui:

  • entre o thread do mixer normal e o thread do mixer rápido no AudioFlinger
  • entre o thread de retorno de chamada do aplicativo para um AudioTrack rápido e um thread de mixer rápido (ambos têm prioridade elevada, mas prioridades ligeiramente diferentes)
  • entre o thread de retorno de chamada do aplicativo para um AudioRecord rápido e o thread de captura rápida (semelhante ao anterior)
  • dentro da implementação de Hardware Abstraction Layer (HAL) de áudio, por exemplo, para telefonia ou cancelamento de eco
  • dentro do driver de áudio no kernel
  • entre o thread de retorno de chamada AudioTrack ou AudioRecord e outros threads de aplicativo (isso está fora de nosso controle)

Soluções comuns

As soluções típicas incluem:

  • desabilitando interrupções
  • mutexes de herança de prioridade

Desativar interrupções não é viável no espaço do usuário do Linux e não funciona para Multi-Processadores Simétricos (SMP).

Futexes de herança de prioridade (mutexes rápidos do espaço do usuário) estão disponíveis no kernel do Linux, mas não são expostos atualmente pela biblioteca de tempo de execução do Android C Bionic . Eles não são usados ​​no sistema de áudio porque são relativamente pesados ​​e porque dependem de um cliente confiável.

Técnicas usadas pelo Android

Os experimentos começaram com "tentar bloquear" e bloquear com tempo limite. Estas são variantes de bloqueio não bloqueantes e limitadas da operação mutex lock. Tentar bloquear e bloquear com tempo limite funcionou muito bem, mas eram suscetíveis a alguns modos de falha obscuros: o servidor não tinha garantia de acesso ao estado compartilhado se o cliente estivesse ocupado e o tempo limite cumulativo poderia ser muito longo se houve uma longa sequência de bloqueios não relacionados que atingiram o tempo limite.

Também usamos operações atômicas , como:

  • incremento
  • bit a bit "ou"
  • bit a bit "e"

Todos eles retornam o valor anterior e incluem as barreiras SMP necessárias. A desvantagem é que eles podem exigir novas tentativas ilimitadas. Na prática, descobrimos que as novas tentativas não são um problema.

Nota: As operações atômicas e suas interações com barreiras de memória são notoriamente mal interpretadas e usadas incorretamente. Incluímos esses métodos aqui para fins de integridade, mas recomendamos que você leia também o artigo SMP Primer para Android para obter mais informações.

Ainda temos e usamos a maioria das ferramentas acima e adicionamos recentemente estas técnicas:

  • Use filas FIFO de leitor único e gravador sem bloqueio para dados.
  • Tente copiar o estado em vez de compartilhar o estado entre os módulos de alta e baixa prioridade.
  • Quando o estado precisar ser compartilhado, limite o estado à palavra de tamanho máximo que pode ser acessada atomicamente em uma operação de barramento sem novas tentativas.
  • Para estados complexos com várias palavras, use uma fila de estados. Uma fila de estado é basicamente apenas uma fila FIFO de um único leitor e um gravador sem bloqueio, usada para estado em vez de dados, exceto que o gravador recolhe pushs adjacentes em um único push.
  • Preste atenção às barreiras de memória para correção do SMP.
  • Confie, mas verifique . Ao compartilhar o estado entre os processos, não presuma que o estado está bem formado. Por exemplo, verifique se os índices estão dentro dos limites. Esta verificação não é necessária entre threads no mesmo processo, entre processos de confiança mútua (que normalmente têm o mesmo UID). Também é desnecessário para dados compartilhados, como áudio PCM, onde a corrupção é irrelevante.

Algoritmos sem bloqueio

Algoritmos sem bloqueio têm sido objeto de estudos muito recentes. Mas, com exceção das filas FIFO de leitor único e gravador, descobrimos que são complexas e sujeitas a erros.

A partir do Android 4.2, você pode encontrar nossas classes não bloqueadoras de leitor / gravador único nestes locais:

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

Eles foram projetados especificamente para AudioFlinger e não são de uso geral. Os algoritmos sem bloqueio são notórios por serem difíceis de depurar. Você pode olhar para este código como um modelo. Mas esteja ciente de que pode haver bugs e não é garantido que as classes sejam adequadas para outros fins.

Para desenvolvedores, alguns dos exemplos de código do aplicativo OpenSL ES devem ser atualizados para usar algoritmos de não bloqueio ou fazer referência a uma biblioteca de código aberto não Android.

Publicamos um exemplo de implementação de FIFO sem bloqueio que é projetado especificamente para código de aplicativo. Veja esses arquivos localizados no diretório de origem da plataforma frameworks/av/audio_utils :

Ferramentas

Até onde sabemos, não existem ferramentas automáticas para encontrar inversões de prioridade, especialmente antes que isso aconteça. Algumas ferramentas de análise de código estático de pesquisa são capazes de encontrar inversões de prioridade se puderem acessar toda a base de código. Claro, se o código de usuário arbitrário estiver envolvido (como está aqui para o aplicativo) ou for uma grande base de código (como para o kernel do Linux e drivers de dispositivo), a análise estática pode ser impraticável. O mais importante é ler o código com muito cuidado e ter uma boa compreensão de todo o 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 avisa com antecedência.

Uma palavra final

Depois de toda essa discussão, não tenha medo de mutexes. Mutexes são seus amigos para uso comum, quando usados ​​e implementados corretamente em casos de uso comuns não críticos. Mas entre tarefas de alta e baixa prioridade e em sistemas sensíveis ao tempo, os mutexes têm mais probabilidade de causar problemas.