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. A passagem do FUSE tem suporte nos kernels android12-5.4, android12-5.10 e android-mainline (somente para teste). Isso significa que o suporte a esse recurso depende do kernel usado pelo dispositivo e da versão do Android em que o dispositivo está executando:

  • Os dispositivos que fizeram upgrade do Android 11 para o 12 não podem oferecer suporte à passagem do FUSE, já que 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 passagem 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 Android que implementa o passthrough 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 desativou o SDCardFS e tornou 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, bem como 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 inferior, 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 de espaço do usuário seguem este caminho: espaço do usuário → VFS → sdcardfs → VFS → ext4 → cache/armazenamento da 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 alguma 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 do FUSE recebe uma solicitação para abrir um arquivo, ele decide se a passagem do FUSE deve estar disponível para esse arquivo específico. Se 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 de 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 do FUSE concluirá a solicitação aberta, o driver do FUSE processará a resposta do daemon do FUSE e uma referência ao arquivo do sistema de arquivos inferior será adicionada ao arquivo FUSE dentro do kernel. Quando um app solicita uma operação de leitura/gravação em um arquivo FUSE, o driver do FUSE verifica se a referência a um arquivo de sistema de arquivos anterior 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 anteriormente o passthrough do FUSE, a desativação impedirá que o dispositivo use o passthrough do FUSE, mas ele continuará funcional.

Para ativar/desativar o passthrough do 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 a passagem do FUSE esteja em uso.

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

atest CtsStorageTest