Usar IPC de vinculação

Esta página descreve as mudanças no driver de vinculação no Android 8, fornece detalhes sobre o uso do IPC de vinculação e lista a política SELinux necessária.

Mudanças no driver de vinculação

A partir do Android 8, o framework do Android e as HALs agora se comunicam entre si usando o binder. Como essa comunicação aumenta drasticamente o tráfego de vinculação, o Android 8 inclui várias melhorias projetadas para manter o IPC de vinculação rápido. Os fornecedores de SoC e OEMs precisam mesclar diretamente das ramificações relevantes de android-4.4, android-4.9 e versões mais recentes do projeto kernel/common.

Vários domínios de vinculação (contextos)

Common-4.4 e versões mais recentes, incluindo upstream

Para dividir o tráfego do binder entre o framework (independente do dispositivo) e o código do fornecedor (específico do dispositivo), o Android 8 introduziu o conceito de um contexto de binder. Cada contexto de vinculação tem o próprio nó de dispositivo e o próprio gerenciador de contexto (serviço). Só é possível acessar o gerenciador de contexto pelo nó do dispositivo ao qual ele pertence. Quando um nó de vinculação é transmitido por um determinado contexto, ele só pode ser acessado por outro processo nesse mesmo contexto, isolando completamente os domínios uns dos outros. Para saber mais sobre o uso, consulte vndbinder e vndservicemanager.

Dispersão e reunião

Common-4.4 e versões mais recentes, incluindo upstream

Nas versões anteriores do Android, cada parte de dados em uma chamada de vinculação era copiada três vezes:

  • Uma vez para serializar em um Parcel no processo de chamada
  • No driver do kernel, copie o Parcel para o processo alvo.
  • Uma vez para desserializar o Parcel no processo de destino

O Android 8 usa a otimização de dispersão-coleção para reduzir o número de cópias de 3 para 1. Em vez de serializar os dados em um Parcel primeiro, os dados permanecem na estrutura original e no layout de memória, e o driver os copia imediatamente para o processo de destino. Depois que os dados estão no processo de destino, a estrutura e o layout da memória são os mesmos, e os dados podem ser lidos sem exigir outra cópia.

Bloqueio granular

Common-4.4 e versões mais recentes, incluindo upstream

Em versões anteriores do Android, o driver de vinculação usava uma trava global para proteger contra acessos simultâneos a estruturas de dados importantes. Embora houvesse uma disputa mínima pelo bloqueio, o problema principal era que, se uma linha de execução de baixa prioridade recebeu o bloqueio e foi interrompida, isso poderia atrasar seriamente linhas de execução de alta prioridade que precisavam receber o mesmo bloqueio. Isso causou instabilidade na plataforma.

As tentativas iniciais para resolver esse problema envolviam desativar a preempção enquanto mantinha a trava global. No entanto, isso foi mais um hack do que uma solução real, e acabou sendo rejeitado pela upstream e descartado. As tentativas seguintes se concentraram em tornar o bloqueio mais refinado, uma versão que está sendo executada em dispositivos Pixel desde janeiro de 2017. Embora a maioria dessas mudanças tenha sido publicada, melhorias substanciais foram feitas nas versões seguintes.

Depois de identificar pequenos problemas na implementação de bloqueio de granularidade fina, criamos uma solução melhorada com uma arquitetura de bloqueio diferente e enviamos as mudanças em todos os ramos comuns do kernel. Continuamos testando essa implementação em um grande número de dispositivos diferentes. Como não temos conhecimento de problemas pendentes, essa é a implementação recomendada para dispositivos enviados com o Android 8.

Herança de prioridade em tempo real

Common-4.4 e common-4.9 (upstream em breve)

O driver de vinculação sempre ofereceu uma boa herança de prioridade. Como um número cada vez maior de processos no Android é executado com prioridade em tempo real, em alguns casos, agora faz sentido que, se uma linha de execução em tempo real fizer uma chamada de vinculação, a linha de execução no processo que processa essa chamada também será executada com prioridade em tempo real. Para oferecer suporte a esses casos de uso, o Android 8 agora implementa a herança de prioridade em tempo real no driver de vinculação.

Além da herança de prioridade no nível da transação, a herança de prioridade de nó permite que um nó (objeto de serviço de vinculação) especifique uma prioridade mínima em que as chamadas para esse nó precisam ser executadas. As versões anteriores do Android já ofereciam suporte à herança de prioridade de nó com valores adequados, mas o Android 8 adiciona suporte à herança de nó de políticas de programação em tempo real.

Mudanças no espaço do usuário

O Android 8 inclui todas as mudanças no espaço do usuário necessárias para trabalhar com o driver de vinculação atual no kernel comum, com uma exceção: a implementação original para desativar a herança de prioridade em tempo real para /dev/binder usava um ioctl. O desenvolvimento subsequente mudou o controle da herança de prioridade para um método mais refinado que é por modo de vinculação (e não por contexto). Portanto, o ioctl não está na ramificação comum do Android e é enviado nos nossos kernels comuns.

O efeito dessa mudança é que a herança de prioridade em tempo real é desativada por padrão para todos os nós. A equipe de desempenho do Android descobriu que é benéfico ativar a herança de prioridade em tempo real para todos os nós no domínio hwbinder. Para conseguir o mesmo efeito, escolha essa mudança no espaço do usuário.

SHAs para kernels comuns

Para fazer as mudanças necessárias no driver de vinculação, sincronize com o SHA apropriado:

  • Common-3.18
    cc8b90c121de ANDROID: binder: não verifica as permissões de prio na restauração.
  • Common-4.4
    76b376eac7a2 ANDROID: binder: não verifica as permissões de prio na restauração.
  • Common-4.9
    ecd972d4f9b5 ANDROID: binder: não verifica as permissões de prio na restauração.

Trabalhar com IPC de vinculação

Historicamente, os processos do fornecedor usavam a comunicação interprocesso de vinculação (IPC, na sigla em inglês) para se comunicar. No Android 8, o nó de dispositivo /dev/binder passa a ser exclusivo para processos de framework, o que significa que os processos do fornecedor não têm mais acesso a ele. Os processos do fornecedor podem acessar /dev/hwbinder, mas precisam converter as interfaces AIDL para usar a HIDL. Para fornecedores que querem continuar usando interfaces AIDL entre processos do fornecedor, o Android oferece suporte ao IPC de vinculação, conforme descrito abaixo. No Android 10, a AIDL estável permite que todos os processos usem /dev/binder, além de resolver as garantias de estabilidade HIDL e /dev/hwbinder. Para saber como usar o AIDL estável, consulte AIDL para HALs.

vndbinder

O Android 8 oferece suporte a um novo domínio de vinculação para uso por serviços do fornecedor, acessado usando /dev/vndbinder em vez de /dev/binder. Com a adição de /dev/vndbinder, o Android agora tem os três domínios IPC a seguir:

Domínio do IPC Descrição
/dev/binder IPC entre processos de framework/app com interfaces AIDL
/dev/hwbinder IPC entre processos de framework/fornecedor com interfaces HIDL
IPC entre processos de fornecedor com interfaces HIDL
/dev/vndbinder IPC entre processos de fornecedor/fornecedor com interfaces AIDL

Para que /dev/vndbinder apareça, verifique se o item de configuração do kernel CONFIG_ANDROID_BINDER_DEVICES está definido como "binder,hwbinder,vndbinder" (este é o padrão nas árvores de kernel comuns do Android).

Normalmente, os processos do fornecedor não abrem o driver de vinculação diretamente, mas fazem a vinculação com a biblioteca de espaço do usuário libbinder, que abre o driver de vinculação. Adicionar um método para ::android::ProcessState() seleciona o driver de vinculação para libbinder. Os processos do fornecedor precisam chamar esse método antes de chamar ProcessState, IPCThreadState ou de fazer qualquer chamada de vinculação em geral. Para usar, faça a seguinte chamada após o main() de um processo do fornecedor (cliente e servidor):

ProcessState::initWithDriver("/dev/vndbinder");

vndservicemanager

Antes, os serviços de vinculação eram registrados com servicemanager, onde podiam ser recuperados por outros processos. No Android 8, servicemanager agora é usado exclusivamente por processos de framework e app, e os processos do fornecedor não podem mais acessá-lo.

No entanto, os serviços do fornecedor agora podem usar vndservicemanager, uma nova instância de servicemanager que usa /dev/vndbinder em vez de /dev/binder e que é criada com as mesmas origens do framework servicemanager. Os processos do fornecedor não precisam fazer mudanças para se comunicar com vndservicemanager. Quando um processo do fornecedor abre /dev/vndbinder, as pesquisas de serviço vão automaticamente para vndservicemanager.

O binário vndservicemanager está incluído nos makefiles padrão do dispositivo do Android.

Política do SELinux

Os processos do fornecedor que querem usar a funcionalidade de vinculação para se comunicar entre si precisam do seguinte:

  1. Acesso a /dev/vndbinder.
  2. O binder {transfer, call} se conecta a vndservicemanager.
  3. binder_call(A, B) para qualquer domínio de fornecedor A que queira chamar o domínio de fornecedor B pela interface de vinculação de fornecedores.
  4. Permissão para serviços {add, find} em vndservicemanager.

Para atender aos requisitos 1 e 2, use a macro vndbinder_use():

vndbinder_use(some_vendor_process_domain);

Para atender ao requisito 3, o binder_call(A, B) para os processos do fornecedor A e B que precisam se comunicar por meio de um binder pode permanecer no lugar e não precisa ser renomeado.

Para atender ao requisito 4, você precisa fazer mudanças na forma como os nomes de serviço, os rótulos de serviço e as regras são processados.

Para mais detalhes sobre o SELinux, consulte Security-Enhanced Linux no Android. Para saber mais sobre o SELinux no Android 8.0, consulte SELinux para Android 8.0.

Nomes de serviços

Anteriormente, o fornecedor processava nomes de serviço registrados em um arquivo service_contexts e adicionava regras correspondentes para acessar esse arquivo. Exemplo de arquivo service_contexts de device/google/marlin/sepolicy:

AtCmdFwd                              u:object_r:atfwd_service:s0
cneservice                            u:object_r:cne_service:s0
qti.ims.connectionmanagerservice      u:object_r:imscm_service:s0
rcs                                   u:object_r:radio_service:s0
uce                                   u:object_r:uce_service:s0
vendor.qcom.PeripheralManager         u:object_r:per_mgr_service:s0

No Android 8, o vndservicemanager carrega o arquivo vndservice_contexts. Os serviços do fornecedor que estão migrando para vndservicemanager (e que já estão no arquivo service_contexts antigo) precisam ser adicionados ao novo arquivo vndservice_contexts.

Rótulos de serviço

Anteriormente, os rótulos de serviço, como u:object_r:atfwd_service:s0, eram definidos em um arquivo service.te. Exemplo:

type atfwd_service,      service_manager_type;

No Android 8, é necessário mudar o tipo para vndservice_manager_type e mover a regra para o arquivo vndservice.te. Exemplo:

type atfwd_service,      vndservice_manager_type;

regras do servicemanager

Antes, as regras davam aos domínios acesso para adicionar ou encontrar serviços do servicemanager. Exemplo:

allow atfwd atfwd_service:service_manager find;
allow some_vendor_app atfwd_service:service_manager add;

No Android 8, essas regras podem permanecer no lugar e usar a mesma classe. Exemplo:

allow atfwd atfwd_service:service_manager find;
allow some_vendor_app atfwd_service:service_manager add;