Passagem do FUSE

O Android 12 oferece suporte ao FUSE de passagem, que minimiza a sobrecarga do FUSE para alcançar um desempenho comparável ao acesso direto ao sistema de arquivos inferior. O passthrough do FUSE tem suporte nos kernels android12-5.4, android12-5.10 e android-mainline (somente para testes), o que significa que o suporte a esse recurso depende do kernel usado pelo dispositivo e da versão do Android em execução:

  • Os dispositivos que fazem upgrade do Android 11 para o Android 12 não podem oferecer suporte ao passthrough do FUSE, porque os kernels desses dispositivos estão congelados e não podem ser movidos para um kernel que foi oficialmente atualizado com as mudanças de passthrough do FUSE.

  • Os dispositivos lançados com o Android 12 podem oferecer suporte ao encaminhamento do FUSE ao usar um kernel oficial. Para esses dispositivos, o código do framework do Android que implementa o passthrough do FUSE é incorporado ao módulo principal MediaProvider, que é atualizado automaticamente. Dispositivos que não implementam o MediaProvider como um módulo principal (por exemplo, dispositivos Android Go) também podem acessar as mudanças do MediaProvider conforme são compartilhadas publicamente.

FUSE x SDCardFS

O sistema de arquivos no espaço do usuário (FUSE, na sigla em inglês) é um mecanismo que permite que as operações realizadas em um sistema de arquivos FUSE sejam terceirizadas pelo kernel (driver FUSE) para um programa de espaço do usuário (demônio FUSE), que implementa as operações. O Android 11 descontinua o SDCardFS e torna o FUSE a solução padrão para emulação de armazenamento. Como parte dessa mudança, o Android implementou o próprio daemon FUSE para interceptar acessos a arquivos, aplicar recursos extras de segurança e privacidade e manipular arquivos no momento da execução.

Embora o FUSE tenha um bom desempenho ao lidar com informações em cache, como páginas ou atributos, ele introduz regressões de desempenho ao acessar o armazenamento externo, que são especialmente visíveis em dispositivos de nível médio e baixo. Essas regressões são causadas por uma cadeia de componentes que cooperam na implementação do sistema de arquivos FUSE, além de várias mudanças do espaço do kernel para o espaço do usuário nas comunicações entre o driver FUSE e o daemon FUSE (em comparação com o acesso direto ao sistema de arquivos mais baixo que é mais simples e completamente implementado no kernel).

Para atenuar essas regressões, os apps podem usar junção para reduzir a cópia de dados e usar a API ContentProvider para ter acesso direto a arquivos de sistemas de arquivos mais baixos. Mesmo com essas e outras otimizações, as operações de leitura e gravação podem ter uma largura de banda reduzida ao usar o FUSE em comparação com o acesso direto ao sistema de arquivos inferior, especialmente com operações de leitura aleatórias, em que nenhum armazenamento em cache ou leitura antecipada pode ajudar. E os apps que acessam diretamente o armazenamento pelo caminho /sdcard/ legados continuam apresentando quedas de desempenho perceptíveis, especialmente ao realizar operações intensivas de E/S.

Solicitações de espaço do usuário do SDcardFS

O uso do SDcardFS pode acelerar a emulação de armazenamento e as verificações de permissão do FUSE removendo a chamada de espaço do usuário do kernel. As solicitações do espaço do usuário seguem o caminho: espaço do usuário → VFS → sdcardfs → VFS → ext4 → armazenamento/cache de página.

FUSE Passthrough SDcardFS

Figura 1. Solicitações de espaço do usuário do SDcardFS

Solicitações do espaço do usuário do FUSE

O FUSE foi usado inicialmente para ativar a emulação de armazenamento e permitir que os apps usassem o armazenamento interno ou um cartão SD externo de forma transparente. O uso do FUSE introduz uma sobrecarga porque cada solicitação do espaço do usuário segue o caminho: espaço do usuário → VFS → driver FUSE → daemon FUSE → VFS → ext4 → armazenamento/cache de página.

FUSE de passagem

Figura 2. Solicitações do espaço do usuário do FUSE

Solicitações de passagem do FUSE

A maioria das permissões de acesso a arquivos é verificada no momento da abertura do arquivo, com outras verificações de permissões ocorrendo durante a leitura e gravação nesse arquivo. Em alguns casos, é possível saber no momento da abertura do arquivo que o app solicitante tem acesso total ao arquivo solicitado. Assim, o sistema não precisa continuar encaminhando as solicitações de leitura e gravação do driver FUSE para o daemon FUSE, já que isso apenas move dados de um lugar para outro.

Com o FUSE passthrough, o daemon FUSE que processa uma solicitação aberta pode notificar o driver FUSE de que a operação é permitida e que todas as solicitações de leitura e gravação posteriores podem ser encaminhadas diretamente para o sistema de arquivos de nível inferior. Isso evita a sobrecarga extra de esperar que o daemon FUSE do espaço do usuário responda às solicitações do driver FUSE.

Confira abaixo uma comparação entre as solicitações de FUSE e FUSE passthrough.

Comparação de passagem do FUSE

Figura 3. Solicitação FUSE x solicitação de passagem FUSE

Quando um app realiza um acesso ao sistema de arquivos FUSE, as seguintes operações ocorrem:

  1. O driver FUSE processa e enfileira a solicitação, depois a apresenta ao demônio FUSE que processa esse sistema de arquivos FUSE por uma instância de conexão específica no arquivo /dev/fuse, que o demônio FUSE bloqueia para leitura.

  2. Quando o daemon FUSE recebe uma solicitação para abrir um arquivo, ele decide se o passthrough do FUSE estará disponível para esse arquivo específico. Se ele estiver disponível, o daemon:

    1. Notifica o driver FUSE sobre essa solicitação.

    2. Ativa o encaminhamento FUSE para o arquivo usando o ioctl FUSE_DEV_IOC_PASSTHROUGH_OPEN, que precisa ser executado no descritor de arquivo do /dev/fuse aberto.

  3. O ioctl recebe (como parâmetro) uma estrutura de dados que contém o seguinte:

    • Descriptor de arquivo do arquivo do sistema de arquivos inferior que é o destino do recurso de transferência.

    • Identificador exclusivo da solicitação FUSE que está sendo processada (precisa estar aberta ou criar e abrir).

    • Campos extras que podem ser deixados em branco e são destinados a implementações futuras.

  4. Se o ioctl for bem-sucedido, o daemon FUSE vai concluir a solicitação de abertura, o driver FUSE vai processar a resposta do daemon FUSE e uma referência ao arquivo do sistema de arquivos mais baixo será adicionada ao arquivo FUSE no kernel. Quando um app solicita uma operação de leitura/gravação em um arquivo FUSE, o driver FUSE verifica se a referência a um arquivo de sistema de arquivos mais baixo está disponível.

    • Se uma referência estiver disponível, o driver criará uma nova solicitação de sistema de arquivos virtual (VFS, na sigla em inglês) com os mesmos parâmetros direcionados ao arquivo de sistema de arquivos inferior.

    • Se uma referência não estiver disponível, o driver vai encaminhar a solicitação para o daemon FUSE.

As operações acima ocorrem para leitura/gravação e leitura-iter/gravação-iter em arquivos genéricos e operações de leitura/gravação em arquivos mapeados em memória. O encaminhamento FUSE para um arquivo específico existe até que ele seja fechado.

Implementar a visualização externa do FUSE

Para ativar o encaminhamento FUSE em dispositivos com o Android 12, adicione as linhas abaixo ao arquivo $ANDROID_BUILD_TOP/device/…/device.mk do dispositivo de destino.

# Use FUSE passthrough
PRODUCT_PRODUCT_PROPERTIES += \
    persist.sys.fuse.passthrough.enable=true

Para desativar o encaminhamento FUSE, omita a mudança de configuração acima ou defina persist.sys.fuse.passthrough.enable como false. Se você tiver ativado o passthrough do FUSE anteriormente, a desativação impedirá que o dispositivo use o passthrough do FUSE, mas ele continuará funcional.

Para ativar/desativar o passthrough FUSE sem atualizar o dispositivo, mude a propriedade do sistema usando comandos ADB. Veja um exemplo abaixo.

adb root
adb shell setprop persist.sys.fuse.passthrough.enable {true,false}
adb reboot

Para mais ajuda, consulte a implementação de referência.

Validar o passthrough do FUSE

Para validar se o MediaProvider está usando o FUSE passthrough, verifique logcat para mensagens de depuração. Exemplo:

adb logcat FuseDaemon:V \*:S
--------- beginning of main
03-02 12:09:57.833  3499  3773 I FuseDaemon: Using FUSE passthrough
03-02 12:09:57.833  3499  3773 I FuseDaemon: Starting fuse...

A entrada FuseDaemon: Using FUSE passthrough no registro garante que o FUSE esteja em uso.

O CTS do Android 12 inclui CtsStorageTest, que inclui testes que acionam o passthrough do FUSE. Para executar o teste manualmente, use atest, conforme mostrado abaixo:

atest CtsStorageTest