Dykk inn i SDCardFS: Hvordan Googles FUSE-erstatning vil redusere I/O-overhead

En dyptgående utforskning av SDCardFS, Googles erstatning for FUSE, og hvordan implementeringen vil redusere I/O-overhead.

For flere måneder siden la Google til noe som heter "SDCardFS” til de offisielle AOSP-grenene for Linux-kjernen. På den tiden ble flyttingen bare lagt merke til av noen kjerneutviklere, men fløy ellers under radaren til de fleste brukere. Ingen overraskelse der med tanke på det faktum at de fleste brukere, inkludert meg selv, egentlig ikke vet hva som foregår under panseret til Android OS og dets kjerne.

Imidlertid er den siste episoden av Android-utviklere Backstage podcast fornyet interesse for dette emnet. Podcasten, arrangert av Chet Haase (en senior programvareingeniør hos Google), utforsket nylige og kommende endringer gjort i kjernen. På showet var en Linux-kjerneutvikler som jobbet på Android-teamet - Rom Lemarchand. Duoen diskuterte først og fremst hvilke endringer som ble gjort for å imøtekomme A/B-oppdateringer, men i de siste 5 minuttene av episoden snakket Lemarchand om "den neste store tingen" som teamet hans jobbet med - SDCardFS.

Jeg må innrømme at jeg lærte om eksistensen av SDCardFS etter å ha lyttet til denne podcasten. Selvfølgelig var jeg ikke den eneste som interesserte meg for dette emnet, som en nylig Reddit-tråd har vist. Jeg var imidlertid ikke fornøyd med den grunnleggende forklaringen som ble tilbudt i podcasten, og i et forsøk på å fjerne noe av feilinformasjon ble spredt rundt, gjorde jeg litt egen undersøkelse og snakket med noen få eksperter med relevant kunnskap om saken.

Stor takk til programvareutvikleren Michal Kowalczyk for å ha bidratt med sin kunnskap til denne artikkelen og for at du tok deg tid til å svare på spørsmålene mine.


"Ekstern" er virkelig intern

Med en gang, er det garantert noen misoppfatninger vi må rydde opp i - ellers vil resten av artikkelen være veldig forvirrende. Det er nyttig å diskutere historien til SD-kort og Android-telefoner.

I de tidlige dagene av Android-telefoner stolte nesten alle enheter på å bruke microSD-kortene for lagring. Dette skyldtes det faktum at telefoner på den tiden ble levert med minimal intern lagringskapasitet. Imidlertid gir SD-kort som brukes til å lagre applikasjoner ofte ikke en fantastisk brukeropplevelse, i hvert fall sammenlignet med hastigheten som internt flashminne kan lese/skrive data med. Derfor ble den økende bruken av SD-kort for ekstern datalagring en bekymring for brukeropplevelsen for Google.

På grunn av den tidlige spredningen av SD-kort som eksterne lagringsenheter, var Androids navnekonvensjoner for lagring basert på det faktum at hver enhet hadde et faktisk, fysisk microSD-kortspor. Men selv på enheter som ikke inneholdt et SD-kortspor, ble /sdcard-etiketten fortsatt brukt for å peke på den faktiske interne lagringsbrikken. Mer forvirrende er det faktum at enheter som brukte både et fysisk SD-kort og en høykapasitets lagringsbrikke for lagring, ofte vil navngi partisjonene sine basert på SD-kortet. For eksempel, i disse enhetene vil /sdcard-monteringspunktet referere til den faktiske interne lagringsbrikken, mens noe som /storage/sdcard1 vil referere til det fysiske eksterne kortet.

Selv om microSD-kortet praktisk talt anses for å være ekstern lagring, resulterte navnekonvensjonen i at "SDCard" ble stående lenge forbi enhver faktisk bruk av et fysisk kort. Denne forvirringen med lagring ga også applikasjonsutviklere en del hodepine på grunn av det faktum at applikasjonsdata og dens media ble atskilt mellom de to partisjonene.

Den lave lagringsplassen til tidlige interne lagringsbrikker resulterte i at brukere frustrerende fant ut at de ikke lenger kunne installere applikasjoner (på grunn av at /data-partisjonen er full). I mellomtiden ble deres microSD-kort med større kapasitet henvist til kun å inneholde medier (som bilder, musikk og filmer). Brukere som surfet på forumene våre på den tiden husker kanskje disse navnene: Link2SD og Apps2SD. Dette var (rot)løsninger som gjorde det mulig for brukere å installere applikasjonene sine og dataene på det fysiske SD-kortet. Men dette var langt fra perfekte løsninger, så Google måtte gå inn.

Berømt, Google trakk pluggen på SD-kort veldig tidlig. Nexus One er fortsatt den eneste Nexus-enheten med et microSD-kortspor (og det vil det for alltid være siden Nexus-merket faktisk er dødt). Med Nexus S var det nå bare én enhetlig partisjon for lagring av alle applikasjonsdata og media - /data-partisjonen. Det som en gang var kjent som /sdcard-monteringspunktet nå refererte ganske enkelt til et virtuelt filsystem (implementert under LUNTE protokoll som diskutert nedenfor) plassert i datapartisjonen - /data/media/0.

For å opprettholde kompatibilitet og redusere forvirring, brukte Google fortsatt denne nå virtuelle "sdcard"-partisjonen for å holde media. Men nå som denne virtuelle "sdcard"-partisjonen faktisk var plassert i /data, ville alt som er lagret i den, telle mot lagringsplassen til den interne lagringsbrikken. Dermed var det opp til OEM-er å vurdere hvor mye plass de skulle allokere til applikasjoner (/data) kontra media (/data/media).

To veldig forskjellige "SD-kort"

Google håpet på at produsentene skulle følge deres eksempel og bli kvitt SD-kort. Heldigvis klarte telefonprodusenter over tid å kjøpe disse komponentene med høyere kapasitet mens de forble kostnadseffektive, så behovet for SD-kort begynte å bli tynt. Men navnekonvensjonene har vedvart for å redusere mengden innsats som utviklere og OEM-er må gjøre for å justere. For øyeblikket, når vi refererer til "ekstern lagring" refererer vi til enten en av to ting: det faktiske flyttbare microSD-kortet eller den virtuelle "SDCard"-partisjonen som ligger i /data/media. Den siste av disse, praktisk talt, er faktisk intern lagring, men Googles navnekonvensjon skiller den på grunn av det faktum at disse dataene er tilgjengelige for brukeren (for eksempel når den er koblet til datamaskinen).

For øyeblikket, når vi refererer til "ekstern lagring" refererer vi til enten en av to ting: det faktiske flyttbare microSD-kortet eller den virtuelle "SDCard"-partisjonen som ligger i /data/media.


Historien om Androids virtuelle filsystemer

Nå som "sdcard" blir behandlet som et virtuelt filsystem, betydde det at det kunne formateres som et hvilket som helst filsystem som Google ønsket. Fra og med Nexus S og Android 2.3 valgte Google å formatere «sdcard» som VFAT (virtuell FAT). Dette trekket var fornuftig på den tiden, siden montering av VFAT ville tillate nesten hvilken som helst datamaskin å få tilgang til dataene som er lagret på telefonen din. Imidlertid var det to store problemer med denne første implementeringen.

Den første gjelder først og fremst sluttbrukeren (deg). For å koble enheten til datamaskinen, bruker du USB-masselagringsmodus for å overføre data. Dette krevde imidlertid at Android-enheten demonterte den virtuelle partisjonen før datamaskinen fikk tilgang til dataene. Hvis en bruker ønsket å bruke enheten mens den er koblet til, vil mange ting vises som utilgjengelige.

De introduksjon av Media Transfer Protocol (MTP) løste dette første problemet. Når den er koblet til, ser datamaskinen enheten din som en "medielagringsenhet". Den ber om en liste over filer fra telefonen din, og MTP returnerer en liste over filer som datamaskinen kan laste ned fra enheten. Når en fil blir bedt om å bli slettet, sender MTP en kommando for å fjerne den forespurte filen fra lagringen. I motsetning til USB-masselagringsmodus som faktisk monterer "sd-kortet", lar MTP brukeren fortsette å bruke enheten mens den er koblet til. Videre spiller ikke filsystemet på Android-telefonen lenger noen rolle for datamaskinen å gjenkjenne filene på enheten.

For det andre var det det faktum at VFAT ikke ga den typen robuste tillatelsesadministrasjon som Google trengte. Tidlig ville mange applikasjonsutviklere behandle "sdcard" som en dumpingplass for applikasjonens data, uten en enhetlig følelse av hvor de skulle lagre filene sine. Mange applikasjoner ville ganske enkelt opprette en mappe med appnavnet og lagre filene der.

Nesten hver applikasjon der ute på det tidspunktet krevde WRITE_EXTERNAL_STORAGE tillatelse til å skrive applikasjonsfilene til den eksterne lagringen. Det som imidlertid var mer urovekkende var det faktum at nesten alle applikasjoner også krevde det READ_EXTERNAL_STORAGE tillatelse - bare for å lese sine egne datafiler! Dette betydde at applikasjoner enkelt kunne ha tilgang til data lagret hvor som helst på den eksterne lagringen, og en slik tillatelse ble ofte gitt av brukeren fordi det var påkrevd for mange apper å ha det funksjon.

Google så tydelig på dette som problematisk. Hele ideen bak tillatelsesadministrasjon er å separere hva apper kan og ikke har tilgang til. Hvis nesten alle apper får lesetilgang til potensielt sensitive brukerdata, er tillatelsen meningsløs. Derfor bestemte Google at de trengte en ny tilnærming. Det er der FUSE kommer inn.


Filsystem i brukerrom (FUSE)

Fra og med Android 4.4 bestemte Google seg for å ikke lenger montere den virtuelle "sdcard"-partisjonen som VFAT. I stedet begynte Google å bruke FUSE for å emulere FAT32 på den virtuelle "sdcard"-partisjonen. Med sdcard-programmet ringer FUSE for å emulere katalogtillatelser i FAT-on-sdcard-stil, kan applikasjoner begynne å få tilgang til dataene som er lagret på ekstern lagring uten å kreve noen tillatelser. Fra og med API-nivå 19 var det faktisk ikke lenger nødvendig med READ_EXTERNAL_STORAGE for å få tilgang til filene på ekstern lagring - forutsatt at datamappen opprettet av FUSE-demonen samsvarer med appens pakkenavn. FUSE ville håndtere syntetisere eier, gruppe og moduser for filer på ekstern lagring når en applikasjon er installert.

FUSE skiller seg fra moduler i kjernen ettersom den tillater ikke-privilegerte brukere å skrive virtuelle filsystemer. Grunnen til at Google implementerte FUSE er ganske enkel - den gjorde det de ville og var allerede godt forstått og dokumentert i Linux-verdenen. For å sitere en Google-utvikler om saken:

"Fordi FUSE er en fin stabil API, er det i hovedsak null vedlikeholdsarbeid som kreves når du flytter mellom kjerneversjoner. Hvis vi migrerte til en kjerneløsning, ville vi registrert oss for å vedlikeholde et sett med patcher for hver stabile kjerneversjon." -Jeff Sharkey, programvareingeniør hos Google

Det begynte imidlertid å bli ganske tydelig at FUSEs overhead introduserte en hit i ytelse blant andre problemer. Utvikleren jeg snakket med angående denne saken, Michal Kowalczyk, skrevet et utmerket blogginnlegg over et år siden med detaljer om de aktuelle problemene med FUSE. Flere tekniske detaljer kan leses på bloggen hans, men jeg vil beskrive funnene hans (med hans tillatelse) i mer lekmannstermer.


Problemet med FUSE

I Android bruker "sdcard"-brukerromsdemonen FUSE for å montere /dev/fuse til den emulerte eksterne lagringskatalogen ved oppstart. Etter det spør sdcard-demonen FUSE-enheten for eventuelle ventende meldinger fra kjernen. Hvis du hørte på podcasten, har du kanskje hørt Mr. Lemarchand referere til FUSE som introduserer overhead under I/O-operasjoner - her er i hovedsak hva som skjer.

I den virkelige verden påvirker denne prestasjonshiten noen fil lagret på ekstern lagring.

Problem #1 - I/O Overhead

La oss si at vi lager en enkel tekstfil, kalt "test.txt", og lagrer den i /sdcard/test.txt (som Jeg minner deg om, er faktisk /data/media/0/test.txt forutsatt at den nåværende brukeren er den primære brukeren på enhet). Hvis vi ønsket å lese (command cat) denne filen, ville vi forvente at systemet utstedte 3 kommandoer: åpne, les og lukk. Faktisk, som Mr. Kowalczyk demonstrerer å bruke strace, det er det som skjer:

Men fordi filen er plassert på den eksterne lagringen som administreres av sdcard-demonen, er det mange ekstra operasjoner som må utføres. Ifølge Mr. Kowalczyk er det i hovedsak 8 ekstra trinn som trengs for hver av disse 3 individuelle kommandoene:

  1. Userspace-applikasjonen utsteder systemanrop som vil bli håndtert av FUSE-driveren i kjernen (vi ser det i den første strace-utgangen)
  2. FUSE-driver i kjernen varsler userspace daemon (sdcard) om ny forespørsel
  3. Userspace daemon leser /dev/fuse
  4. Userspace daemon analyserer kommando og gjenkjenner filoperasjon (f.eks. åpen)
  5. Userspace daemon sender ut systemkall til det faktiske filsystemet (EXT4)
  6. Kernel håndterer fysisk datatilgang og sender data tilbake til brukerområdet
  7. Userspace modifiserer (eller ikke) data og sender dem gjennom /dev/fuse til kjernen igjen
  8. Kjernen fullfører det originale systemanropet og flytter data til den faktiske brukerromsapplikasjonen (i vårt eksempel katt)

Dette virker som mye av overhead bare til en enkelt I/O-kommando som skal kjøres. Og du ville ha rett. For å demonstrere dette forsøkte Mr. Kowalczyk to forskjellige I/O-tester: en som involverer kopiering av en stor fil og den andre kopiering av mange små filer. Han sammenlignet hastigheten til FUSE (på den virtuelle partisjonen montert som FAT32) som håndterer disse operasjonene versus kjernen (på datapartisjonen formatert som EXT4), og han fant ut at FUSE faktisk bidro betydelig overhead.

I den første testen kopierte han en 725MB fil under begge testforholdene. Han fant ut at FUSE-implementeringen overførte store filer 17 % saktere.

I den andre testen kopierte han 10 000 filer - hver av dem var på 5 KB. I dette scenariet var FUSE-implementeringen over 40 sekunder langsommere å kopiere i utgangspunktet 50 MB med data.

I den virkelige verden påvirker denne prestasjonshiten noen fil lagret på ekstern lagring. Dette betyr apper som kart som lagrer store filer på /sdcard, musikkapper som lagrer tonnevis av musikkfiler, kameraapper og bilder, etc. Enhver I/O-operasjon som utføres som involverer ekstern lagring, påvirkes av FUSEs overhead. Men I/O-overhead er ikke det eneste problemet med FUSE.

Problem #2 - Dobbel bufring

Bufring av data er viktig for å forbedre ytelsen til datatilgang. Ved å lagre viktige databiter i minnet, kan Linux-kjernen raskt hente disse dataene når det trengs. Men på grunn av måten FUSE er implementert på, lagrer Android dobbelt så mye cache som trengs.

Som Mr. Kowalczyk demonstrerer, forventes en 10 MB fil å bli lagret i hurtigbuffer som nøyaktig 10 MB, men i stedet opp til hurtigbufferstørrelse med rundt 20 MB. Dette er problematisk på enheter med mindre RAM, siden Linux-kjernelagrene bruker sidebuffer til å lagre data i hukommelse. Mr. Kowalczyk testet dette dobbeltbufringsproblemet ved å bruke denne tilnærmingen:

  1. Lag en fil med kjent størrelse (for testing, 10 MB)
  2. Kopier det til /sdcard
  3. Slipp sidebufferen
  4. Ta et øyeblikksbilde av bruken av sidebufferen
  5. Les testfilen
  6. Ta et nytt øyeblikksbilde av bruken av sidebufferen

Det han fant var at før testen hans ble 241 MB brukt av kjernen for sidebuffer. Når han leste testfilen sin, forventet han å se 251 MB brukt til sidebuffer. I stedet fant han ut at den kjernen brukte 263 MB for sidebuffer - ca dobbelt så mye som forventet. Grunnen til at dette skjer er fordi dataene først bufres av brukerapplikasjonen som opprinnelig utstedte I/O-kallet (FUSE), og deretter av sdcard-demonen (EXT4 FS).

Problem #3 - Ufullstendig implementering av FAT32

Det er ytterligere to problemer som stammer fra bruken av FUSE som emulerer FAT32 som er mindre kjent i Android-fellesskapet.

Den første involverer feil tidsstempler. Hvis du noen gang har overført en fil (for eksempel et bilde) og lagt merke til at tidsstemplet er feil, er det på grunn av Androids implementering av FUSE. Dette problemet har eksisterte for år. For å være mer spesifikk involverer problemet utime() systemanrop som lar deg endre tilgangs- og endringstiden for en fil. Dessverre har ikke anrop til sdcard-demonen som en standardbruker riktig tillatelse til å utføre dette systemanropet. Det finnes løsninger for dette, men de krever at du gjør det har root-tilgang.

Hvis du noen gang har overført en fil (for eksempel et bilde) og lagt merke til at tidsstemplet er feil, er det på grunn av Androids implementering av FUSE.

Det neste problemet er mer bekymringsfullt for bedrifter som bruker noe som en smartSD-kort. Før FUSE kunne app-produsenter overvåke O_DIRECT flagg for å kommunisere med en innebygd mikrokontroller i kortet. Med FUSE kan utviklere bare få tilgang til den hurtigbufrede versjonen av en fil, og kan ikke se noen kommandoer sendt av en mikrokontroller. Dette er problematisk for enkelte bedrifts-/myndighets-/bankapper som kommuniserer med verdiøkende microSD-kort.


Dumper SIKRING for SDCardFS

Noen OEM-er oppdaget disse problemene tidlig, og begynte å se etter en kjerneløsning for å erstatte FUSE. Samsung utviklet for eksempel SDCardFS som er basert på WrapFS. Denne kjerneløsningen emulerer FAT32 akkurat som FUSE gjør, men gir avkall på I/O-overhead, dobbel caching og andre problemer jeg har nevnt ovenfor. (Ja, la meg gjenta det poenget, denne løsningen som Google nå implementerer er basert på Samsungs arbeid).

Google selv har endelig erkjent ulempene forbundet med FUSE, og det er grunnen til at de har begynt å bevege seg mot det innebygde FAT32-emuleringslaget utviklet av Samsung. Selskapet, som nevnt i Android-utviklere Backstage podcast, har jobbet med å gjøre SDCardFS tilgjengelig for alle enheter i en kommende versjon av kjernen. Du kan for øyeblikket se fremgangen til deres jobber i AOSP.

Som en Google-utvikler forklart tidligere, er den største utfordringen med å implementere en in-kernel-løsning hvordan man tilordner pakkenavnet til applikasjons-ID nødvendig for at en pakke skal få tilgang til sine egne data i ekstern lagring uten å kreve noe tillatelser. Men den uttalelsen ble gitt for et år siden, og vi har nådd det punktet hvor teamet kaller SDCardFS sin "neste store ting." De har allerede bekreftet at fryktet tidsstempelfeil har blitt fikset, takket være at vi flyttet bort fra FUSE, så vi kan se frem til å se alle endringene som ble ført frem etter at FUSE ble forlatt.


Misoppfatninger om faktasjekking

Hvis du er nådd så langt inn i artikkelen, takk for at du har fulgt med på alt så langt! Jeg ønsket å avklare noen spørsmål jeg hadde selv da jeg skrev denne artikkelen:

  • SDCardFS har ingenting å gjøre med faktiske SD-kort. Den er bare navngitt som sådan fordi den håndterer I/O-tilgang for /sdcard. Og som du kanskje husker, er /sdcard en utdatert etikett som refererer til den "eksterne" lagringen til enheten din (der apper lagrer media).
  • SDCardFS er ikke et tradisjonelt filsystem som FAT32, EXT4 eller F2FS. Det er et stabelbart wrapper-filsystem som sender kommandoer til de lavere, emulerte filsystemene (i dette tilfellet ville det være FAT32 på /sd-kortet).
  • Ingenting vil endre seg med hensyn til MTP. Du vil fortsette å bruke MTP for å overføre filer til/fra datamaskinen din (til Google bestemmer seg for en bedre protokoll). Men i det minste vil tidsstempelfeilen bli fikset!
  • Som nevnt før, når Google refererer til "ekstern lagring" snakker de enten om (for alle hensikter og formål) intern /sdcard virtuell FAT32-partisjon ELLER de snakker om en faktisk, fysisk, flyttbar microSD kort. Terminologien er forvirrende, men det er det vi blir slått av.

Konklusjon

Ved å gå bort fra FUSE og implementere et FAT32-emuleringslag i kjernen (SDCardFS), vil Google redusere betydelig I/O-overhead, eliminerer dobbel caching og løser noen obskure problemer knyttet til FUSEs emulering av FAT32.

Siden disse endringene vil bli gjort på en kjerne, kan de rulles ut uten en større ny versjon av Android ved siden av. Noen brukere forventer å se disse endringene offisielt implementert i Android 8, men det er mulig for enhver fremtidig OTA på en Pixel-enhet for å bringe Linux-kjernen versjon 4.1 som Google har jobbet på.

For noen av dere er ikke SDCardFS et nytt konsept. Faktisk har Samsung-enheter brukt det i årevis (det var tross alt de som utviklet det). Helt siden SDCardFS ble introdusert i AOSP i fjor, har noen tilpassede ROM- og kjerneutviklere valgt å implementere det i arbeidet sitt. CyanogenMOD vurderte på et tidspunkt å implementere det, men rullet det tilbake når brukere fikk problemer med bildene sine. Men forhåpentligvis når Google tar styringen over dette prosjektet, kan Android-brukere på alle fremtidige enheter dra nytte av forbedringene som ble introdusert med å forlate FUSE.