Dykker ned i SDCardFS: Hvordan Googles FUSE-udskiftning vil reducere I/O-overhead

En dybdegående udforskning af SDCardFS, Googles erstatning for FUSE, og hvordan implementeringen heraf vil reducere I/O-overhead.

For flere måneder siden tilføjede Google noget kaldet "SDCardFS” til de officielle AOSP-grene til Linux-kernen. Dengang blev flytningen kun bemærket af nogle kerneudviklere, men fløj ellers under de fleste brugeres radar. Ingen overraskelse der i betragtning af, at de fleste brugere, inklusive mig selv, ikke rigtig ved, hvad der foregår under motorhjelmen på Android OS og dets kerne.

Men den seneste episode af Android-udviklere Backstage podcast fornyet interesse for dette emne. Podcasten, der er hostet af Chet Haase (en senior softwareingeniør hos Google), undersøgte de seneste og kommende ændringer foretaget i kernen. På showet var en Linux-kerneudvikler, der arbejdede på Android-teamet - Rom Lemarchand. Duoen diskuterede primært, hvilke ændringer der blev foretaget for at imødekomme A/B-opdateringer, men i de sidste 5 minutter af episoden talte Mr. Lemarchand om "den næste store ting", som hans team arbejdede på - SDCardFS.

Jeg må indrømme, at jeg lærte om eksistensen af ​​SDCardFS efter at have lyttet til denne podcast. Selvfølgelig var jeg ikke den eneste, der interesserede mig for dette emne, som en seneste Reddit-tråd har vist. Jeg var dog ikke tilfreds med den grundlæggende forklaring, der blev tilbudt i podcasten, og i et forsøg på at fjerne nogle af de misinformation blev spredt rundt omkring, lavede jeg selv nogle undersøgelser og talte med et par eksperter med relevant viden om stof.

Stor tak til softwareudvikler Michal Kowalczyk for at bidrage med sin viden til denne artikel og for at tage sig tid til at besvare mine spørgsmål.


"Ekstern" er virkelig intern

Lige fra starten er der helt sikkert nogle misforståelser, vi er nødt til at rydde op i - ellers vil resten af ​​artiklen være meget forvirrende. Det er nyttigt at diskutere historien om SD-kort og Android-telefoner.

I de tidlige dage af Android-telefoner var næsten alle enheder afhængige af at bruge deres microSD-kort til opbevaring. Dette skyldtes det faktum, at telefoner på det tidspunkt blev leveret med minimal intern lagerkapacitet. SD-kort, der bruges til lagring af applikationer, giver dog ofte ikke en fremragende brugeroplevelse, i det mindste sammenlignet med den hastighed, hvormed intern flash-hukommelse kan læse/skrive data. Derfor var den stigende brug af SD-kort til ekstern datalagring ved at blive en bekymring for brugeroplevelsen for Google.

På grund af den tidlige udbredelse af SD-kort som eksterne lagerenheder, var Androids navngivningskonventioner for lager baseret på det faktum, at hver enhed havde en faktisk, fysisk microSD-kortplads. Men selv på enheder, der ikke indeholdt en SD-kortplads, blev /sdcard-etiketten stadig brugt til at pege på den faktiske interne lagerchip. Mere forvirrende er det faktum, at enheder, der brugte både et fysisk SD-kort såvel som en højkapacitetslagringschip til opbevaring, ofte navngiver deres partitioner baseret på SD-kortet. For eksempel vil /sdcard-monteringspunktet i disse enheder referere til den faktiske interne lagerchip, hvorimod noget som /storage/sdcard1 vil referere til det fysiske eksterne kort.

Selvom microSD-kortet praktisk talt anses for at være eksternt lager, resulterede navngivningskonventionen i, at "SDCard" blev hængende længe efter enhver faktisk brug af et fysisk kort. Denne forvirring med lagring gav også en vis hovedpine for applikationsudviklere på grund af det faktum, at applikationsdata og dets medier var adskilt mellem de to partitioner.

Den lave lagerplads på tidlige interne lagerchips resulterede i, at brugere frustrerende fandt ud af, at de ikke længere kunne installere applikationer (på grund af at /data-partitionen er fuld). I mellemtiden blev deres microSD-kort med større kapacitet henvist til kun at indeholde medier (såsom fotos, musik og film). Brugere, der gennemsøgte vores fora dengang, husker muligvis disse navne: Link2SD og Apps2SD. Disse var (rod)løsninger, der gjorde det muligt for brugere at installere deres applikationer og dets data på det fysiske SD-kort. Men det var langt fra perfekte løsninger, så Google måtte træde til.

Berømt, Google trak stikket på SD-kort meget tidligt. Nexus One forbliver den eneste Nexus-enhed med en microSD-kortplads (og det vil det for evigt være, da Nexus-mærket faktisk er dødt). Med Nexus S var der nu kun én samlet partition til lagring af alle applikationsdata og medier - /data-partitionen. Det, der engang var kendt som /sdcard-monteringspunktet, refererede simpelthen til et virtuelt filsystem (implementeret under SIKRING protokol som diskuteret nedenfor) placeret i datapartitionen - /data/media/0.

For at opretholde kompatibilitet og reducere forvirring brugte Google stadig denne nu virtuelle "sdcard"-partition til at holde medier. Men nu hvor denne "sdcard" virtuelle partition faktisk var placeret i /data, ville alt, der er gemt i den, tælle med i lagerpladsen på den interne lagerchip. Det var således op til OEM'er at overveje, hvor meget plads der skulle allokeres til applikationer (/data) kontra medier (/data/media).

To meget forskellige "SD-kort"

Google håbede på, at producenterne ville følge deres eksempel og slippe af med SD-kort. Heldigvis var telefonproducenter over tid i stand til at købe disse komponenter med højere kapacitet, mens de forblev omkostningseffektive, så behovet for SD-kort begyndte at løbe tyndt. Men navnekonventionerne har bestået for at reducere mængden af ​​indsats, som udviklere og OEM'er skal gøre for at justere. I øjeblikket, når vi refererer til "ekstern opbevaring", henviser vi til enten en af ​​to ting: det faktiske flytbare microSD-kort eller den virtuelle "SDCard"-partition placeret i /data/media. Sidstnævnte af disse, praktisk talt, er faktisk internt lager, men Googles navnekonvention adskiller det på grund af det faktum, at disse data er tilgængelige for brugeren (såsom når de er tilsluttet computeren).

I øjeblikket, når vi refererer til "ekstern opbevaring", henviser vi til enten en af ​​to ting: det faktiske flytbare microSD-kort eller den virtuelle "SDCard"-partition placeret i /data/media.


Historien om Androids virtuelle filsystemer

Nu hvor "sdcard" behandles som et virtuelt filsystem, betød det, at det kunne formateres som et hvilket som helst filsystem, som Google ønskede. Startende med Nexus S og Android 2.3 valgte Google at formatere "sdcard" som VFAT (virtuel FAT). Dette træk gav mening på det tidspunkt, da montering af VFAT ville give næsten enhver computer adgang til de data, der er gemt på din telefon. Der var dog to store problemer med denne indledende implementering.

Den første vedrører primært slutbrugeren (dig). For at tilslutte din enhed til din computer skal du bruge USB Mass Storage Mode til at overføre data. Dette krævede dog, at Android-enheden afmonterede den virtuelle partition, før computeren kunne få adgang til dataene. Hvis en bruger ville bruge deres enhed, mens den var tilsluttet, ville mange ting vise sig at være utilgængelige.

Det introduktion af Media Transfer Protocol (MTP) løste dette første problem. Når den er tilsluttet, ser din computer din enhed som en "medielagringsenhed". Den anmoder om en liste over filer fra din telefon, og MTP returnerer en liste over filer, som computeren kan downloade fra enheden. Når en fil anmodes om at blive slettet, sender MTP en kommando for at fjerne den anmodede fil fra lageret. I modsætning til USB Mass Storage Mode, som faktisk monterer "sdcard", giver MTP brugeren mulighed for at fortsætte med at bruge deres enhed, mens den er tilsluttet. Ydermere har filsystemet på Android-telefonen ikke længere betydning for, at computeren kan genkende filerne på enheden.

For det andet var der det faktum, at VFAT ikke leverede den form for robust tilladelsesstyring, som Google havde brug for. Tidligt ville mange applikationsudviklere behandle "sdcard" som en dumpingplads for deres applikations data uden en samlet fornemmelse af, hvor de skulle gemme deres filer. Mange applikationer ville simpelthen oprette en mappe med dens appnavn og gemme dens filer der.

Næsten alle applikationer derude på det tidspunkt krævede WRITE_EXTERNAL_STORAGE tilladelse til at skrive deres applikationsfiler til det eksterne lager. Hvad der dog var mere bekymrende var det faktum, at næsten alle applikationer også krævede READ_EXTERNAL_STORAGE tilladelse - bare for at læse deres egne datafiler! Dette betød, at applikationer nemt kunne få adgang til data gemt hvor som helst på det eksterne lager, og en sådan tilladelse blev ofte givet af brugeren, fordi det var påkrævet for mange apps at kunne endda fungere.

Google så klart dette som problematisk. Hele ideen bag tilladelsesstyring er at adskille, hvad apps kan og ikke har adgang til. Hvis næsten alle apper får læseadgang til potentielt følsomme brugerdata, er tilladelsen meningsløs. Derfor besluttede Google, at de havde brug for en ny tilgang. Det er her FUSE kommer ind.


Filsystem i brugerrum (FUSE)

Startende med Android 4.4 besluttede Google ikke længere at montere den virtuelle "sdcard"-partition som VFAT. I stedet begyndte Google at bruge FUSE til at emulere FAT32 på den virtuelle "sdcard"-partition. Med sdcard-programmet kalder FUSE til at emulere mappetilladelser i FAT-on-sdcard-stil, kunne applikationer begynde at få adgang til dets data, der er gemt på eksternt lager uden at kræve nogen tilladelser. Fra og med API-niveau 19 var READ_EXTERNAL_STORAGE faktisk ikke længere påkrævet for at få adgang til filer placeret på eksternt lager - forudsat at datamappen oprettet af FUSE-dæmonen matcher appens pakkenavn. FUSE ville klare syntetisere ejer, gruppe og tilstande af filer på eksternt lager når en applikation er installeret.

FUSE adskiller sig fra in-kernel-moduler, da det giver ikke-privilegerede brugere mulighed for at skrive virtuelle filsystemer. Grunden til, at Google implementerede FUSE, er ret simpel - det gjorde, hvad de ville og allerede var godt forstået og dokumenteret i Linux-verdenen. For at citere en Google-udvikler om sagen:

"Fordi FUSE er en dejlig stabil API, er der i det væsentlige nul vedligeholdelsesarbejde påkrævet, når du flytter mellem kerneversioner. Hvis vi migrerede til en in-kernel-løsning, ville vi tilmelde os for at vedligeholde et sæt patches for hver stabil kerneversion." -Jeff Sharkey, Software Engineer hos Google

Det var dog ved at blive helt klart, at FUSEs overhead introducerede et hit i ydeevnen blandt andre problemer. Udvikleren, jeg talte med om denne sag, Michal Kowalczyk, skrevet et fremragende blogindlæg for over et år siden med detaljerede oplysninger om de aktuelle problemer med FUSE. Flere tekniske detaljer kan læses på hans blog, men jeg vil beskrive hans resultater (med hans tilladelse) i mere lægmandssprog.


Problemet med FUSE

I Android bruger "sdcard"-brugerpladsdæmonen FUSE til at montere /dev/fuse til den emulerede eksterne lagermappe ved opstart. Derefter spørger sdcard-dæmonen FUSE-enheden for eventuelle ventende beskeder fra kernen. Hvis du lyttede til podcasten, har du måske hørt Mr. Lemarchand henvise til FUSE, der introducerer overhead under I/O-operationer - her er i det væsentlige, hvad der sker.

I den virkelige verden påvirker dette præstationshit nogen fil gemt på eksternt lager.

Problem #1 - I/O Overhead

Lad os sige, at vi opretter en simpel tekstfil, kaldet "test.txt", og gemmer den i /sdcard/test.txt (hvilket jeg minde dig om, er faktisk /data/media/0/test.txt forudsat, at den nuværende bruger er den primære bruger på enhed). Hvis vi ønskede at læse (command cat) denne fil, ville vi forvente, at systemet udsender 3 kommandoer: åben, læs og luk derefter. Faktisk, som hr. Kowalczyk demonstrerer at bruge strace, det er hvad der sker:

Men fordi filen er placeret på det eksterne lager, som styres af sdcard-dæmonen, er der mange yderligere handlinger, der skal udføres. Ifølge Mr. Kowalczyk er der i det væsentlige 8 yderligere trin nødvendige for hver af disse 3 individuelle kommandoer:

  1. Userspace-applikationen udsteder systemkald, der vil blive håndteret af FUSE-driveren i kernen (vi ser det i den første strace-output)
  2. FUSE-driver i kernen giver brugerspace-dæmon (sdcard) besked om ny anmodning
  3. Userspace daemon læser /dev/fuse
  4. Userspace daemon analyserer kommandoen og genkender filoperation (f.eks. åben)
  5. Userspace daemon udsteder systemkald til det faktiske filsystem (EXT4)
  6. Kernel håndterer fysisk dataadgang og sender data tilbage til brugerområdet
  7. Userspace ændrer (eller ej) data og sender dem gennem /dev/fuse til kernen igen
  8. Kernel fuldfører det originale systemkald og flytter data til den faktiske brugerrumsapplikation (i vores eksempel kat)

Det virker som en masse af overhead blot til en enkelt I/O-kommando, der skal køres. Og du ville have ret. For at demonstrere dette forsøgte hr. Kowalczyk to forskellige I/O-tests: den ene involverede kopiering af en stor fil og den anden kopiering af en masse små filer. Han sammenlignede hastigheden af ​​FUSE (på den virtuelle partition monteret som FAT32), der håndterede disse operationer versus kerne (på datapartitionen formateret som EXT4), og han fandt ud af, at FUSE faktisk bidrog væsentligt over hovedet.

I den første test kopierede han en 725MB fil under begge testbetingelser. Han fandt ud af, at FUSE-implementeringen overførte store filer 17 % langsommere.

I den anden test kopierede han 10.000 filer - hver af dem 5 KB i størrelse. I dette scenarie var FUSE-implementeringen slut 40 sekunder langsommere at kopiere stort set 50MBs data.

I den virkelige verden påvirker dette præstationshit nogen fil gemt på eksternt lager. Det betyder apps som Maps, der gemmer store filer på /sdcard, Musik-apps, der gemmer tonsvis af musikfiler, Kamera-apps og fotos osv. Enhver I/O-operation, der udføres, der involverer det eksterne lager, påvirkes af FUSEs overhead. Men I/O-overhead er ikke det eneste problem med FUSE.

Problem #2 - Dobbelt cachelagring

Caching af data er vigtig for at forbedre dataadgangsydelsen. Ved at gemme væsentlige stykker data i hukommelsen er Linux-kernen i stand til hurtigt at genkalde disse data, når det er nødvendigt. Men på grund af den måde, FUSE er implementeret på, lagrer Android den dobbelte mængde cache, der er nødvendig.

Som hr. Kowalczyk demonstrerer, forventes en 10MB fil at blive gemt i cache som præcis 10 MB, men i stedet op til cachestørrelse med omkring 20 MB. Dette er problematisk på enheder med mindre RAM, da Linux-kernelagrene bruger sidecache til at gemme data i hukommelse. Mr. Kowalczyk testede dette problem med dobbelt caching ved at bruge denne tilgang:

  1. Opret en fil med en kendt størrelse (til test, 10 MB)
  2. Kopier det til /sdcard
  3. Slip sidecachen
  4. Tag et øjebliksbillede af brugen af ​​sidens cache
  5. Læs testfilen
  6. Tag endnu et øjebliksbillede af brugen af ​​sidens cache

Hvad han fandt var, at forud for hans test blev 241 MB brugt af kernen til sidecache. Når han havde læst sin testfil, forventede han at se 251 MB brugt til sidecache. I stedet fandt han ud af, at den kerne brugte 263 MB til sidecache - ca dobbelt så meget som forventet. Årsagen til dette sker, er fordi dataene først cachelagres af det brugerprogram, der oprindeligt udstedte I/O-kaldet (FUSE), og dernæst af sdcard-dæmonen (EXT4 FS).

Problem #3 - Ufuldstændig implementering af FAT32

Der er yderligere to problemer, der stammer fra brugen af ​​FUSE, der emulerer FAT32, som er mindre kendt i Android-fællesskabet.

Den første involverer forkerte tidsstempler. Hvis du nogensinde har overført en fil (såsom et billede) og bemærket, at tidsstemplet er forkert, er det på grund af Androids implementering af FUSE. Dette spørgsmål har eksisterede for flere år. For at være mere specifik involverer spørgsmålet utime() systemkald, som giver dig mulighed for at ændre adgangs- og ændringstidspunktet for en fil. Desværre har opkald foretaget til sdcard-dæmonen som standardbruger ikke den rette tilladelse til at udføre dette systemkald. Der er løsninger på dette, men de kræver, at du gør det har root-adgang.

Hvis du nogensinde har overført en fil (såsom et billede) og bemærket, at tidsstemplet er forkert, er det på grund af Androids implementering af FUSE.

Det næste problem er mere bekymrende for virksomheder, der bruger noget som en smartSD-kort. Før FUSE kunne app-producenter overvåge O_DIRECT flag for at kunne kommunikere med en indlejret mikrocontroller i kortet. Med FUSE kan udviklere kun få adgang til den cachelagrede version af en fil og kan ikke se nogen kommandoer sendt af en mikrocontroller. Dette er problematisk for nogle virksomheds-/stats-/bankapps, der kommunikerer med værditilvækst microSD-kort.


Dumper SIKRING til SDCardFS

Nogle OEM'er genkendte disse problemer tidligt og begyndte at lede efter en in-kernel løsning til at erstatte FUSE. Samsung udviklede for eksempel SDCardFS som er baseret på WrapFS. Denne in-kernel løsning emulerer FAT32 ligesom FUSE gør, men giver afkald på I/O overhead, dobbelt caching og andre problemer, jeg har nævnt ovenfor. (Ja, lad mig gentage det punkt, denne løsning, som Google nu implementerer, er baseret på Samsungs arbejde).

Google selv har endelig erkendt ulemperne forbundet med FUSE, hvorfor de er begyndt at bevæge sig mod det in-kernel FAT32-emuleringslag udviklet af Samsung. Selskabet, som nævnt i Android-udviklere Backstage podcast, har arbejdet på at gøre SDCardFS tilgængelig for alle enheder i en kommende version af kernen. Du kan i øjeblikket se deres fremskridt arbejde i AOSP.

Som en Google-udvikler forklaret tidligere, er den største udfordring med at implementere en in-kernel løsning, hvordan man kortlægger pakkenavnet til applikations-ID, der er nødvendigt for, at en pakke kan få adgang til sine egne data i eksternt lager uden at kræve noget tilladelser. Men den udtalelse blev fremsat for et år siden, og vi er nået til det punkt, hvor holdet kalder SDCardFS for deres "næste store ting." De har allerede bekræftet, at frygtede tidsstempelfejl er blevet rettet, takket være at flytte væk fra FUSE, så vi kan se frem til at se alle de ændringer, der er frembragt med opgivelsen af ​​FUSE.


Fejlopfattelse af faktatjek

Hvis du er nået så langt ind i artiklen, så kudos for at følge med alt indtil videre! Jeg ville gerne afklare et par spørgsmål, jeg selv havde, da jeg skrev denne artikel:

  • SDCardFS har intet at gøre med faktiske SD-kort. Det er bare navngivet som sådan, fordi det håndterer I/O-adgang for /sdcard. Og som du måske husker, er /sdcard en forældet etiket, der henviser til den "eksterne" lagring af din enhed (hvor apps gemmer deres medier).
  • SDCardFS er ikke et traditionelt filsystem som FAT32, EXT4 eller F2FS. Det er et stabelbart wrapper-filsystem, der sender kommandoer til de lavere, emulerede filsystemer (i dette tilfælde ville det være FAT32 på /sd-kortet).
  • Intet vil ændre sig med hensyn til MTP. Du vil fortsætte med at bruge MTP til at overføre filer til/fra din computer (indtil Google finder en bedre protokol). Men i det mindste bliver tidsstempelfejlen rettet!
  • Som nævnt før, når Google refererer til "ekstern lagring", taler de enten om (for alle henseender og formål) intern /sdcard virtuel FAT32-partition ELLER de taler om en faktisk, fysisk, flytbar microSD kort. Terminologien er forvirrende, men det er det, vi bliver ramt af.

Konklusion

Ved at flytte væk fra FUSE og implementere et in-kernel FAT32 emulation layer (SDCardFS), vil Google reducere betydelig I/O-overhead, eliminerer dobbelt caching og løser nogle obskure problemer relateret til dens FUSE's emulering af FAT32.

Da disse ændringer vil blive lavet til en kerne, kan de rulles ud uden en større ny version af Android ved siden af. Nogle brugere forventer at se disse ændringer officielt implementeret i Android 8, men det er muligt for enhver fremtidig OTA på en Pixel-enhed for at bringe Linux-kernen version 4.1, som Google har arbejdet på.

For nogle af jer er SDCardFS ikke et nyt koncept. Faktisk har Samsung-enheder brugt det i årevis (det var trods alt dem, der udviklede det). Lige siden SDCardFS blev introduceret i AOSP sidste år, har nogle brugerdefinerede ROM- og kerneudviklere valgt at implementere det i deres arbejde. CyanogenMOD overvejede på et tidspunkt at implementere det, men rullede det tilbage, da brugerne stødte på problemer med deres billeder. Men forhåbentlig, når Google tager magten over dette projekt, kan Android-brugere på alle fremtidige enheder drage fordel af de forbedringer, der blev introduceret med at opgive FUSE.