Mergulhando no SDCardFS: como a substituição do FUSE do Google reduzirá a sobrecarga de E/S

Uma exploração aprofundada do SDCardFS, o substituto do FUSE do Google, e como sua implementação reduzirá a sobrecarga de E/S.

Vários meses atrás, o Google adicionou algo chamado “Cartão SDCFS”Para as ramificações oficiais do AOSP para o kernel Linux. Na época, a mudança foi notada apenas por alguns desenvolvedores de kernel, mas por outro lado passou despercebido pela maioria dos usuários. Nenhuma surpresa, considerando o fato de que a maioria dos usuários, inclusive eu, não sabe realmente o que acontece nos bastidores do sistema operacional Android e de seu kernel.

No entanto, o episódio mais recente do Desenvolvedores Android nos bastidores podcast renovou o interesse neste tópico. O podcast, apresentado por Chet Haase (engenheiro de software sênior do Google), explorou mudanças recentes e futuras feitas no kernel. No programa estava um desenvolvedor de kernel Linux trabalhando na equipe Android - Rom Lemarchand. A dupla discutiu principalmente quais mudanças foram feitas para acomodar as atualizações A/B, mas nos últimos 5 minutos do episódio o Sr. Lemarchand falou sobre “a próxima grande novidade” em que sua equipe estava trabalhando -

Cartão SDCFS.

Devo admitir que descobri a existência do SDCardFS depois de ouvir este podcast. É claro que não fui o único a me interessar por esse assunto, pois tópico recente do Reddit mostrou. No entanto, não fiquei satisfeito com a explicação básica oferecida no podcast e, em um esforço para dissipar alguns dos desinformação sendo espalhada, fiz algumas pesquisas por conta própria e conversei com alguns especialistas com conhecimento relevante sobre o matéria.

Muito obrigado ao desenvolvedor de software Michal Kowalczyk por contribuir com seu conhecimento para este artigo e por dedicar seu tempo para responder às minhas perguntas.


“Externo” é realmente interno

De cara, é provável que haja alguns equívocos que precisamos esclarecer - caso contrário, o restante do artigo será muito confuso. É útil discutir a história dos cartões SD e dos telefones Android.

Nos primeiros dias dos telefones Android, quase todos os dispositivos dependiam do uso de cartões microSD para armazenamento. Isso se devia ao fato de que os telefones da época eram fornecidos com minúsculas capacidades de armazenamento interno. No entanto, os cartões SD usados ​​para armazenar aplicativos geralmente não oferecem uma experiência de usuário excelente, pelo menos em comparação com a velocidade com que a memória flash interna pode ler/gravar dados. Portanto, o uso crescente de cartões SD para armazenamento externo de dados estava se tornando uma preocupação para a experiência do usuário do Google.

Devido à proliferação inicial de cartões SD como dispositivos de armazenamento externo, as convenções de nomenclatura de armazenamento do Android baseavam-se no fato de que cada dispositivo tinha um slot físico real para cartão microSD. Mas mesmo em dispositivos que não continham um slot para cartão SD, o rótulo /sdcard ainda era usado para apontar para o chip de armazenamento interno real. Mais confuso é o fato de que os dispositivos que utilizavam tanto um cartão SD físico quanto um chip de armazenamento de alta capacidade para armazenamento frequentemente nomeariam suas partições com base no cartão SD. Por exemplo, nesses dispositivos, o ponto de montagem /sdcard se referiria ao chip de armazenamento interno real, enquanto algo como /storage/sdcard1 se referiria ao cartão externo físico.

Assim, embora o cartão microSD seja praticamente considerado um armazenamento externo, a convenção de nomenclatura resultou na permanência do “SDCard” por muito tempo após qualquer uso real de um cartão físico. Essa confusão com o armazenamento também proporcionou alguma dor de cabeça aos desenvolvedores de aplicativos devido ao fato de que os dados do aplicativo e sua mídia eram segregados entre as duas partições.

O pouco espaço de armazenamento dos primeiros chips de armazenamento interno fez com que os usuários descobrissem de forma frustrante que não podiam mais instalar aplicativos (devido à partição /data estar cheia). Enquanto isso, seus cartões microSD de maior capacidade foram relegados a armazenar apenas mídia (como fotos, músicas e filmes). Os usuários que navegaram em nossos fóruns no passado podem se lembrar destes nomes: Link2SD e Apps2SD. Essas eram soluções (root) que permitiam aos usuários instalar seus aplicativos e seus dados no cartão SD físico. Mas essas soluções estavam longe de ser perfeitas, então o Google teve que intervir.

Notoriamente, o Google desligou os cartões SD muito cedo. O Nexus One continua sendo o único dispositivo Nexus com slot para cartão microSD (e assim será para sempre, já que a marca Nexus está efetivamente morta). Com o Nexus S, havia agora apenas uma partição unificada para armazenar todos os dados e mídia do aplicativo – a partição /data. O que antes era conhecido como ponto de montagem /sdcard agora se referia simplesmente a um sistema de arquivos virtual (implementado sob o FUSÍVEL conforme discutido abaixo) localizado na partição de dados - /data/media/0.

Para manter a compatibilidade e reduzir a confusão, o Google ainda usou esta partição “sdcard” agora virtual para armazenar mídia. Mas agora que esta partição virtual “sdcard” estava realmente localizada em /data, qualquer coisa armazenada nela contaria para o espaço de armazenamento do chip de armazenamento interno. Assim, cabia aos OEMs considerar quanto espaço alocar para aplicativos (/data) versus mídia (/data/media).

Dois “cartões SD” muito diferentes

O Google esperava que os fabricantes seguissem seu exemplo e se livrassem dos cartões SD. Felizmente, com o tempo, os fabricantes de telefones conseguiram adquirir esses componentes com capacidades mais altas e, ao mesmo tempo, permanecerem econômicos, de modo que a necessidade de cartões SD estava começando a diminuir. Mas as convenções de nomenclatura persistiram para reduzir a quantidade de esforço que os desenvolvedores e OEMs teriam que fazer para se ajustar. Atualmente, quando nos referimos a “armazenamento externo” estamos nos referindo a qualquer uma das duas coisas: o cartão microSD removível real ou a partição virtual “SDCard” localizada em /data/media. Este último, praticamente falando, é na verdade armazenamento interno, mas a convenção de nomenclatura do Google o diferencia pelo fato de esses dados serem acessíveis ao usuário (como quando conectado ao computador).

Atualmente, quando nos referimos a “armazenamento externo” estamos nos referindo a qualquer uma das duas coisas: o cartão microSD removível real ou a partição virtual “SDCard” localizada em /data/media.


A história dos sistemas de arquivos virtuais do Android

Agora que o “sdcard” é tratado como um sistema de arquivos virtual, isso significa que ele pode ser formatado como qualquer sistema de arquivos desejado pelo Google. Começando com o Nexus S e o Android 2.3, o Google optou por formatar o “sdcard” como VFAT (FAT virtual). Essa mudança fazia sentido na época, já que a montagem do VFAT permitiria que praticamente qualquer computador acessasse os dados armazenados no seu telefone. No entanto, houve dois problemas principais com esta implementação inicial.

O primeiro diz respeito principalmente ao usuário final (você). Para conectar seu dispositivo ao computador, você usaria o modo de armazenamento em massa USB para transferir dados. Isso, no entanto, exigia que o dispositivo Android desmontasse a partição virtual antes que o computador pudesse acessar os dados. Se um usuário quisesse usar seu dispositivo enquanto estivesse conectado, muitas coisas apareceriam como indisponíveis.

O introdução do protocolo de transferência de mídia (MTP) resolveu este primeiro problema. Quando conectado, seu computador vê seu dispositivo como um dispositivo de “armazenamento de mídia”. Ele solicita uma lista de arquivos do seu telefone e o MTP retorna uma lista de arquivos que o computador pode baixar do dispositivo. Quando é solicitada a exclusão de um arquivo, o MTP envia um comando para remover o arquivo solicitado do armazenamento. Ao contrário do modo de armazenamento em massa USB, que na verdade monta o “sdcard”, o MTP permite que o usuário continue usando seu dispositivo enquanto estiver conectado. Além disso, o sistema de arquivos presente no telefone Android não importa mais para o computador reconhecer os arquivos no dispositivo.

Em segundo lugar, havia o fato de que o VFAT não fornecia o tipo de gerenciamento robusto de permissões que o Google precisava. No início, muitos desenvolvedores de aplicativos tratavam o “sdcard” como um depósito para os dados de seus aplicativos, sem uma noção unificada de onde armazenar seus arquivos. Muitos aplicativos simplesmente criariam uma pasta com o nome do aplicativo e armazenariam seus arquivos nela.

Quase todas as aplicações existentes na época exigiam o WRITE_EXTERNAL_STORAGE permissão para gravar seus arquivos de aplicativos no armazenamento externo. No entanto, o que era mais preocupante era o facto de quase todas as aplicações também exigirem o READ_EXTERNAL_STORAGE permissão - apenas para ler seus próprios arquivos de dados! Isso significava que os aplicativos poderiam facilmente ter acesso aos dados armazenados em qualquer lugar do armazenamento externo, e essa permissão era frequentemente concedida pelo usuário porque era necessária para muitos aplicativos função.

O Google claramente viu isso como problemático. A ideia por trás do gerenciamento de permissões é segregar quais aplicativos podem ou não ter acesso. Se quase todos os aplicativos tiverem acesso de leitura a dados potencialmente confidenciais do usuário, a permissão não terá sentido. Assim, o Google decidiu que precisava de uma nova abordagem. É aí que entra o FUSE.


Sistema de arquivos no espaço do usuário (FUSE)

A partir do Android 4.4, o Google decidiu não montar mais a partição virtual “sdcard” como VFAT. Em vez disso, o Google começou a usar o FUSE para emular o FAT32 na partição virtual “sdcard”. Com o programa sdcard chamando FUSE para emular permissões de diretório estilo FAT-on-sdcard, os aplicativos poderiam começar a acessar seus dados armazenados em armazenamento externo sem exigir nenhuma permissão. Na verdade, a partir do nível 19 da API, READ_EXTERNAL_STORAGE não era mais necessário para acessar arquivos localizados no armazenamento externo - desde que a pasta de dados criada pelo daemon FUSE corresponda ao nome do pacote do aplicativo. FUSE cuidaria sintetizando o proprietário, grupo e modos de arquivos no armazenamento externo quando um aplicativo é instalado.

O FUSE difere dos módulos do kernel porque permite que usuários não privilegiados escrevam sistemas de arquivos virtuais. A razão pela qual o Google implementou o FUSE é bastante simples: ele fez o que queria e já estava bem compreendido e documentado no mundo do Linux. Para citar um Desenvolvedor do Google sobre o assunto:

"Como o FUSE é uma API estável e agradável, basicamente não é necessário nenhum trabalho de manutenção ao alternar entre versões do kernel. Se migrarmos para uma solução no kernel, estaríamos nos inscrevendo para manter um conjunto de patches para cada versão estável do kernel." -Jeff Sharkey, engenheiro de software do Google

No entanto, estava ficando claro que a sobrecarga do FUSE estava causando um impacto no desempenho, entre outros problemas. O desenvolvedor com quem conversei sobre esse assunto, Michal Kowalczyk, escreveu uma excelente postagem no blog há mais de um ano detalhando os problemas atuais do FUSE. Mais detalhes técnicos podem ser lidos em seu blog, mas descreverei suas descobertas (com sua permissão) em termos mais leigos.


O problema com o FUSE

No Android, o daemon de espaço do usuário “sdcard” utiliza FUSE para montar /dev/fuse no diretório de armazenamento externo emulado na inicialização. Depois disso, o daemon sdcard pesquisa o dispositivo FUSE em busca de mensagens pendentes do kernel. Se você ouviu o podcast, deve ter ouvido o Sr. Lemarchand se referir ao FUSE introduzindo sobrecarga durante operações de E/S - aqui está essencialmente o que acontece.

No mundo real, esse impacto no desempenho afeta qualquer arquivo armazenado em armazenamento externo.

Problema nº 1 - sobrecarga de E/S

Digamos que criamos um arquivo de texto simples, chamado “test.txt”, e o armazenamos em /sdcard/test.txt (que, digamos lembro a você, na verdade é /data/media/0/test.txt assumindo que o usuário atual é o usuário principal no dispositivo). Se quiséssemos ler (comando cat) este arquivo, esperaríamos que o sistema emitisse 3 comandos: abrir, ler e fechar. Na verdade, como o Sr. Kowalczyk demonstra usando traço, é isso que acontece:

Mas como o arquivo está localizado no armazenamento externo gerenciado pelo daemon sdcard, há muitas operações adicionais que precisam ser executadas. De acordo com o Sr. Kowalczyk, existem essencialmente 8 etapas adicionais necessárias para cada um desses 3 comandos individuais:

  1. O aplicativo Userspace emite uma chamada de sistema que será tratada pelo driver FUSE no kernel (vemos isso na primeira saída do strace)
  2. O driver FUSE no kernel notifica o daemon do espaço do usuário (sdcard) sobre uma nova solicitação
  3. O daemon do espaço do usuário lê /dev/fuse
  4. O daemon do espaço do usuário analisa o comando e reconhece a operação do arquivo (ex. abrir)
  5. O daemon do espaço do usuário emite uma chamada de sistema para o sistema de arquivos real (EXT4)
  6. O kernel lida com o acesso físico aos dados e os envia de volta ao espaço do usuário
  7. O espaço do usuário modifica (ou não) os dados e os passa por /dev/fuse para o kernel novamente
  8. O kernel conclui a chamada original do sistema e move os dados para o aplicativo real do espaço do usuário (em nosso exemplo cat)

Isso parece bastante de sobrecarga apenas para um único comando de E/S a ser executado. E você estaria certo. Para demonstrar isso, o Sr. Kowalczyk tentou dois testes de E/S diferentes: um envolvendo a cópia de um arquivo grande e o outro copiando muitos arquivos pequenos. Ele comparou a velocidade do FUSE (na partição virtual montada como FAT32) lidando com essas operações com a kernel (na partição de dados formatada como EXT4), e ele descobriu que o FUSE estava de fato contribuindo significativamente a sobrecarga.

No primeiro teste, ele copiou um arquivo de 725 MB nas duas condições de teste. Ele descobriu que a implementação do FUSE transferia arquivos grandes 17% mais lentamente.

No segundo teste, ele copiou 10.000 arquivos – cada um deles com 5 KB de tamanho. Neste cenário, a implementação do FUSE terminou 40 segundos mais lento para copiar basicamente 50 MB de dados.

No mundo real, esse impacto no desempenho afeta qualquer arquivo armazenado em armazenamento externo. Isso significa aplicativos como Mapas que armazenam arquivos grandes em /sdcard, aplicativos de música que armazenam toneladas de arquivos de música, aplicativos de câmera e fotos, etc. Qualquer operação de E/S realizada que envolva o armazenamento externo é afetada pela sobrecarga do FUSE. Mas a sobrecarga de E/S não é o único problema do FUSE.

Problema nº 2 – Cache Duplo

O armazenamento em cache de dados é importante para melhorar o desempenho do acesso a dados. Ao armazenar dados essenciais na memória, o kernel do Linux é capaz de recuperar rapidamente esses dados quando necessário. Mas devido à forma como o FUSE é implementado, o Android armazena o dobro da quantidade de cache necessária.

Como demonstra o Sr. Kowalczyk, espera-se que um arquivo de 10 MB seja salvo no cache exatamente como 10 MB, mas em vez disso aumente para o tamanho do cache em cerca de 20 MBs. Isso é problemático em dispositivos com menos RAM, pois os armazenamentos do kernel do Linux usam cache de páginas para armazenar dados em memória. Kowalczyk testou esse problema de cache duplo usando esta abordagem:

  1. Crie um arquivo com tamanho conhecido (para teste, 10 MBs)
  2. Copie-o para /sdcard
  3. Solte o cache da página
  4. Tire um instantâneo do uso do cache da página
  5. Leia o arquivo de teste
  6. Tire outro instantâneo do uso do cache da página

O que ele descobriu foi que antes do teste, 241 MB estavam sendo usados ​​pelo kernel para cache de páginas. Depois de ler seu arquivo de teste, ele esperava ver 251 MB utilizados para cache de página. Em vez disso, ele descobriu que aquele kernel estava usando 263 MB para cache de página - sobre o dobro do esperado. A razão pela qual isso ocorre é porque os dados são armazenados em cache primeiro pelo aplicativo do usuário que emitiu originalmente a chamada de E/S (FUSE) e depois pelo daemon sdcard (EXT4 FS).

Problema nº 3 – Implementação incompleta do FAT32

Existem mais dois problemas decorrentes do uso do FUSE emulando FAT32 que são menos conhecidos na comunidade Android.

O primeiro envolve carimbos de data/hora incorretos. Se você já transferiu um arquivo (como uma foto) e percebeu que o carimbo de data/hora está incorreto, é por causa da implementação do FUSE no Android. Esta questão tem existiu para anos. Para ser mais específico, a questão envolve a utime() chamada de sistema que permite alterar o horário de acesso e modificação de um arquivo. Infelizmente, as chamadas feitas ao daemon sdcard como usuário padrão não têm a permissão adequada para executar esta chamada de sistema. Existem soluções alternativas para isso, mas elas exigem que você ter acesso root.

Se você já transferiu um arquivo (como uma foto) e percebeu que o carimbo de data/hora está incorreto, é por causa da implementação do FUSE no Android.

O próximo problema é mais preocupante para empresas que usam algo como um cartão SD inteligente. Antes do FUSE, os fabricantes de aplicativos podiam monitorar o Sinalizador O_DIRECT para se comunicar com um microcontrolador embutido no cartão. Com o FUSE, os desenvolvedores só podem acessar a versão em cache de um arquivo e não conseguem ver nenhum comando enviado por um microcontrolador. Isso é problemático para alguns aplicativos empresariais/governamentais/bancários que se comunicam com cartões microSD de valor agregado.


Despejando FUSE para SDCardFS

Alguns OEMS reconheceram esses problemas desde o início e começaram a procurar uma solução no kernel para substituir o FUSE. A Samsung, por exemplo, desenvolveu Cartão SDCFS que é baseado em WrapFS. Esta solução no kernel emula o FAT32 assim como o FUSE, mas dispensa a sobrecarga de E/S, cache duplo e outros problemas que mencionei acima. (Sim, deixe-me reiterar esse ponto, esta solução que a Google está agora a implementar baseia-se no trabalho da Samsung).

O próprio Google finalmente reconheceu as desvantagens associadas ao FUSE, e é por isso que começou a migrar para a camada de emulação FAT32 no kernel desenvolvida pela Samsung. A empresa, conforme mencionado no Desenvolvedores Android nos bastidores podcast, tem trabalhado para disponibilizar o SDCardFS para todos os dispositivos em uma versão futura do kernel. Atualmente você pode ver o progresso de seus trabalhar na AOSP.

Como um Desenvolvedor do Google explicado anteriormente, o maior desafio na implementação de uma solução no kernel é como mapear o nome do pacote para ID do aplicativo necessário para que um pacote acesse seus próprios dados no armazenamento externo sem exigir qualquer permissões. Mas essa declaração foi feita há um ano e chegamos ao ponto em que a equipe chama o SDCardFS de sua “próxima grande novidade”. Eles já confirmaram que temido erro de carimbo de data/hora foi corrigido, graças ao afastamento do FUSE, então podemos esperar ver todas as mudanças trazidas com o abandono do FUSE.


Equívocos de verificação de fatos

Se você chegou até aqui no artigo, parabéns por acompanhar tudo até agora! Queria esclarecer algumas dúvidas que tive ao escrever este artigo:

  • SDCardFS tem nada a ver com cartões SD reais. Ele é nomeado assim porque lida com o acesso de E/S para /sdcard. E como você deve se lembrar, /sdcard é um rótulo desatualizado que se refere ao armazenamento “externo” do seu dispositivo (onde os aplicativos armazenam sua mídia).
  • SDCardFS é não é um sistema de arquivos tradicional como FAT32, EXT4 ou F2FS. É um sistema de arquivos wrapper empilhável que passa comandos para os sistemas de arquivos emulados inferiores (nesse caso, seria FAT32 no /sdcard).
  • Nada mudará em relação ao MTP. Você continuará usando o MTP para transferir arquivos de/para o seu computador (até que o Google estabeleça um protocolo melhor). Mas pelo menos o erro de carimbo de data/hora será corrigido!
  • Como mencionado anteriormente, quando o Google se refere a "Armazenamento Externo", eles estão falando sobre (para todos os efeitos e propósitos) partição FAT32 virtual interna / sdcard OU eles estão falando sobre um microSD real, físico e removível cartão. A terminologia é confusa, mas é o que nos surpreende.

Conclusão

Ao abandonar o FUSE e implementar uma camada de emulação FAT32 no kernel (SDCardFS), o Google reduzirá sobrecarga significativa de E/S, eliminando o cache duplo e resolvendo alguns problemas obscuros relacionados à emulação de seu FUSE FAT32.

Como essas alterações serão feitas em um kernel, elas poderão ser implementadas sem uma nova versão importante do Android junto com ele. Alguns usuários esperam ver essas mudanças implementadas oficialmente no Android 8, mas é possível para qualquer futuro OTA em um dispositivo Pixel trazer a versão 4.1 do kernel Linux que o Google está trabalhando sobre.

Para alguns de vocês, SDCardFS não é um conceito novo. Na verdade, os dispositivos Samsung já o utilizam há anos (afinal, foram eles que o desenvolveram). Desde que o SDCardFS foi introduzido no AOSP no ano passado, alguns desenvolvedores de ROM e kernel personalizados optaram por implementá-lo em seu trabalho. A certa altura, o CyanogenMOD considerou implementá-lo, mas o reverteu quando os usuários encontraram problemas com suas fotos. Mas esperançosamente, com o Google assumindo o controle deste projeto, os usuários do Android em todos os dispositivos futuros poderão aproveitar as vantagens das melhorias introduzidas com o abandono do FUSE.