Scufundarea în SDCardFS: Cum înlocuirea FUSE de la Google va reduce supraîncărcarea I/O

O explorare aprofundată a SDCardFS, înlocuitorul de la Google pentru FUSE și a modului în care implementarea sa va reduce costul I/O.

Acum câteva luni, Google a adăugat ceva numit „SDcardFS” la ramurile oficiale AOSP pentru nucleul Linux. La momentul respectiv, mișcarea a fost remarcată doar de unii dezvoltatori de kernel, dar altfel a zburat sub radarul majorității utilizatorilor. Nu este surprinzător, având în vedere faptul că majoritatea utilizatorilor, inclusiv eu, nu știu cu adevărat ce se întâmplă sub capota sistemului de operare Android și a nucleului său.

Cu toate acestea, cel mai recent episod din Dezvoltatorii Android în culise podcast-ul a reînnoit interesul pentru acest subiect. Podcastul, găzduit de Chet Haase (un inginer de software senior la Google), a explorat modificările recente și viitoare aduse nucleului. În emisiune a fost un dezvoltator de kernel Linux care lucra în echipa Android - Rom Lemarchand. Duo-ul a discutat în primul rând ce modificări au fost făcute pentru a găzdui actualizările A/B, dar în ultimele 5 minute ale episodului, domnul Lemarchand a vorbit despre „următorul lucru mare” la care lucra echipa sa -

SDcardFS.

Trebuie să recunosc că am aflat despre existența SDCardFS după ce am ascultat acest podcast. Desigur, nu am fost singurul care s-a interesat de acest subiect, ca a thread recent Reddit A apărut. Cu toate acestea, nu am fost mulțumit de explicația de bază care a fost oferită în podcast și într-un efort de a risipi o parte din Dezinformarea fiind răspândită, am făcut unele cercetări proprii și am discutat cu câțiva experți cu cunoștințe relevante despre materie.

Mulțumiri majore dezvoltatorului de software Michal Kowalczyk pentru contribuția cu cunoștințele sale la acest articol și pentru că și-a acordat timp pentru a răspunde la întrebările mele.


„Extern” este cu adevărat intern

De la bun început, trebuie să existe anumite concepții greșite pe care trebuie să le lămurim - altfel, restul articolului va fi foarte confuz. Este util să discutați despre istoria cardurilor SD și a telefoanelor Android.

În primele zile ale telefoanelor Android, aproape fiecare dispozitiv se baza pe utilizarea cardurilor microSD pentru stocare. Acest lucru s-a datorat faptului că telefoanele la acea vreme erau livrate cu capacități de stocare internă minuscule. Cu toate acestea, cardurile SD folosite pentru stocarea aplicațiilor de multe ori nu oferă o experiență de utilizator stelară, cel puțin în comparație cu viteza cu care memoria flash internă poate citi/scrie date. Prin urmare, utilizarea din ce în ce mai mare a cardurilor SD pentru stocarea externă a datelor a devenit o preocupare pentru experiența utilizatorului pentru Google.

Datorită proliferării timpurii a cardurilor SD ca dispozitive de stocare externe, convențiile de denumire a stocării Android s-au bazat pe faptul că fiecare dispozitiv avea un slot fizic real pentru card microSD. Dar chiar și pe dispozitivele care nu conțineau un slot pentru card SD, eticheta /sdcard a fost încă folosită pentru a indica cipul de stocare intern real. Mai confuz este faptul că dispozitivele care utilizau atât un card SD fizic, cât și un cip de stocare de mare capacitate pentru stocare își denumesc adesea partițiile bazate pe cardul SD. De exemplu, în aceste dispozitive, punctul de montare /sdcard s-ar referi la cipul de stocare intern real, în timp ce ceva de genul /storage/sdcard1 s-ar referi la cardul extern fizic.

Astfel, chiar dacă cardul microSD este practic considerat a fi stocare externă, convenția de denumire a dus la ca „SDCard” să rămână de mult peste orice utilizare reală a unui card fizic. Această confuzie cu stocarea a oferit, de asemenea, o bătaie de cap dezvoltatorilor de aplicații, datorită faptului că datele aplicației și mediile sale erau separate între cele două partiții.

Spațiul redus de stocare al cipurilor de stocare interne timpurii a făcut ca utilizatorii să descopere în mod frustrant că nu mai pot instala aplicații (din cauza că partiția /data este plină). Între timp, cardurile lor microSD cu capacitate mai mare au fost lăsate să dețină numai suporturi (cum ar fi fotografii, muzică și filme). Utilizatorii care au răsfoit forumurile noastre în acea zi ar putea să-și amintească aceste nume: Link2SD și Apps2SD. Acestea au fost soluții (root) care le-au permis utilizatorilor să-și instaleze aplicațiile și datele lor pe cardul SD fizic. Dar acestea erau departe de a fi soluții perfecte, așa că Google a trebuit să intervină.

Faimos, Google a scos din priză cardurile SD foarte devreme. Nexus One rămâne singurul dispozitiv Nexus cu slot pentru card microSD (și va fi pentru totdeauna, deoarece marca Nexus este efectiv moartă). Cu Nexus S, exista acum o singură partiție unificată pentru stocarea tuturor datelor și media aplicațiilor - partiția /data. Ceea ce odată era cunoscut ca punct de montare /sdcard acum se referea pur și simplu la un sistem de fișiere virtual (implementat sub SIGURANTA protocol așa cum este discutat mai jos) situat în partiția de date - /data/media/0.

Pentru a menține compatibilitatea și a reduce confuzia, Google a folosit în continuare această partiție „sdcard” acum virtuală pentru a stoca media. Dar acum că această partiție virtuală „sdcard” a fost de fapt localizată în /data, orice stocat în ea ar conta pentru spațiul de stocare al cipul de stocare intern. Astfel, era de latitudinea OEM-urilor să ia în considerare cât spațiu să aloce aplicațiilor (/data) față de media (/data/media).

Două „carduri SD” foarte diferite

Google spera ca producătorii să le urmeze exemplul și să scape de cardurile SD. Din fericire, de-a lungul timpului, producătorii de telefoane au reușit să-și aprovizioneze aceste componente la capacități mai mari, rămânând în același timp rentabili, astfel încât nevoia de carduri SD începea să fie redusă. Dar convențiile de denumire au persistat pentru a reduce cantitatea de efort pe care dezvoltatorii și OEM-urile ar trebui să-l depună pentru a se ajusta. În prezent, când ne referim la „stocare externă” ne referim fie unul dintre cele două lucruri: cardul microSD amovibil real sau partiția virtuală „SDCard” situată în /data/media. Acesta din urmă, practic vorbind, este de fapt stocare internă, dar convenția de denumire a Google o diferențiază datorită faptului că aceste date sunt accesibile utilizatorului (cum ar fi atunci când sunt conectate la computer).

În prezent, când ne referim la „stocare externă” ne referim fie unul dintre cele două lucruri: cardul microSD amovibil real sau partiția virtuală „SDCard” situată în /data/media.


Istoria sistemelor de fișiere virtuale Android

Acum că „sdcard” este tratat ca un sistem de fișiere virtual, însemna că ar putea fi formatat ca orice sistem de fișiere dorit de Google. Începând cu Nexus S și Android 2.3, Google a ales să formateze „sdcard” ca VFAT (FAT virtual). Această mișcare avea sens la momentul respectiv, deoarece montarea VFAT ar permite aproape oricărui computer să acceseze datele stocate pe telefon. Cu toate acestea, au existat două probleme majore cu această implementare inițială.

Prima se referă în primul rând la utilizatorul final (dvs.). Pentru a vă conecta dispozitivul la computer, veți folosi modul USB Mass Storage pentru a transfera date. Acest lucru, totuși, a necesitat ca dispozitivul Android să demonteze partiția virtuală înainte ca computerul să poată accesa datele. Dacă un utilizator ar dori să-și folosească dispozitivul în timp ce este conectat, multe lucruri s-ar arăta ca indisponibile.

The introducerea Protocolului de transfer media (MTP) a rezolvat această primă problemă. Când este conectat, computerul vă vede dispozitivul ca pe un dispozitiv de „stocare media”. Acesta solicită o listă de fișiere de pe telefon, iar MTP returnează o listă de fișiere pe care computerul le poate descărca de pe dispozitiv. Când se solicită ștergerea unui fișier, MTP trimite o comandă pentru a elimina fișierul solicitat din stocare. Spre deosebire de modul de stocare în masă USB care montează de fapt „cardul SD”, MTP permite utilizatorului să continue să-și folosească dispozitivul în timp ce este conectat. Mai mult, sistemul de fișiere prezent pe telefonul Android nu mai contează pentru ca computerul să recunoască fișierele de pe dispozitiv.

În al doilea rând, a existat faptul că VFAT nu a oferit tipul de gestionare robustă a permisiunilor de care avea nevoie Google. La început, mulți dezvoltatori de aplicații au tratat „sdcard” ca pe un teren de gunoi pentru datele aplicației lor, fără un sens unificat de unde să-și stocheze fișierele. Multe aplicații ar crea pur și simplu un folder cu numele aplicației sale și ar stoca fișierele acolo.

Aproape toate aplicațiile existente la momentul respectiv necesitau WRITE_EXTERNAL_STORAGE permisiunea de a-și scrie fișierele aplicației pe stocarea externă. Cu toate acestea, ceea ce a fost mai îngrijorător a fost faptul că aproape fiecare aplicație necesită și READ_EXTERNAL_STORAGE permisiunea - doar pentru a citi propriile fișiere de date! Acest lucru însemna că aplicațiile ar putea avea cu ușurință acces la datele stocate oriunde pe stocarea externă, și o astfel de permisiune a fost adesea acordată de utilizator, deoarece era necesară pentru multe aplicații funcţie.

Google a văzut în mod clar acest lucru ca fiind problematic. Întreaga idee din spatele gestionării permisiunilor este de a segrega aplicațiile la care pot și nu pot avea acces. Dacă aproape fiecărei aplicații i se acordă acces de citire la datele potențial sensibile ale utilizatorilor, atunci permisiunea este lipsită de sens. Astfel, Google a decis că are nevoie de o nouă abordare. Aici intervine FUSE.


Sistem de fișiere în spațiul utilizator (FUSE)

Începând cu Android 4.4, Google a decis să nu mai monteze partiția virtuală „sdcard” ca VFAT. În schimb, Google a început să folosească FUSE pentru a emula FAT32 pe partiția virtuală „sdcard”. Cu apelarea programului sdcard FUSE pentru a emula permisiunile directorului de stil FAT-on-sdcard, aplicațiile ar putea începe să-și acceseze datele stocate pe stocarea externă fără a necesita permisiuni. Într-adevăr, începând cu API Level 19, READ_EXTERNAL_STORAGE nu mai era necesar pentru a accesa fișierele localizate pe stocare externă - cu condiția ca folderul de date creat de demonul FUSE să se potrivească cu numele pachetului aplicației. FUSE s-ar descurca sintetizarea proprietarului, grupului și modurilor fișierelor de pe stocarea externă când este instalată o aplicație.

FUSE diferă de modulele din kernel deoarece permite utilizatorilor fără privilegii să scrie sisteme de fișiere virtuale. Motivul pentru care Google a implementat FUSE este destul de simplu - a făcut ceea ce au vrut și a fost deja bine înțeles și documentat în lumea Linux. Pentru a cita a Dezvoltator Google pe această temă:

„Deoarece FUSE este un API stabil și frumos, în esență nu este necesară nicio lucrare de întreținere atunci când treceți între versiunile de kernel. Dacă am migra la o soluție în kernel, ne-am înscrie pentru a menține un set de patch-uri pentru fiecare versiune stabilă de kernel.” - Jeff Sharkey, inginer software la Google

Cu toate acestea, devenise destul de clar că cheltuielile generale ale lui FUSE introduceau un impact în performanță, printre alte probleme. Dezvoltatorul cu care am vorbit în legătură cu această problemă, Michal Kowalczyk, a scris o postare excelentă pe blog cu peste un an în urmă, detaliind problemele actuale cu FUSE. Mai multe detalii tehnice pot fi citite pe blogul său, dar voi descrie descoperirile sale (cu permisiunea lui) în termeni mai profani.


Problema cu FUSE

În Android, demonul spațiului utilizator „sdcard” utilizează FUSE pentru a monta /dev/fuse în directorul de stocare extern emulat la pornire. După aceea, demonul sdcard interogează dispozitivul FUSE pentru orice mesaj în așteptare din nucleu. Dacă ați ascultat podcast-ul, s-ar putea să-l fi auzit pe domnul Lemarchand referindu-se la introducerea FUSE în timpul operațiunilor I/O - iată ce se întâmplă în esență.

În lumea reală, acest hit de performanță afectează orice fișier stocat pe stocare externă.

Problema #1 - I/O Overhead

Să spunem că creăm un fișier text simplu, numit „test.txt”, și îl stocăm în /sdcard/test.txt (care, să Vă reamintesc, este de fapt /data/media/0/test.txt presupunând că utilizatorul actual este utilizatorul principal pe dispozitiv). Dacă am dori să citim (comandă cat) acest fișier, ne-am aștepta ca sistemul să emită 3 comenzi: deschidere, citire, apoi închidere. Într-adevăr, după cum demonstrează domnul Kowalczyk folosind strace, asa se intampla:

Dar, deoarece fișierul se află pe stocarea externă care este gestionată de demonul sdcard, există multe operațiuni suplimentare care trebuie efectuate. Potrivit domnului Kowalczyk, sunt în esență 8 pași suplimentari necesari pentru fiecare dintre aceste 3 comenzi individuale:

  1. Aplicația Userspace emite un apel de sistem care va fi gestionat de driverul FUSE în kernel (o vedem în prima ieșire strace)
  2. Driverul FUSE din nucleu notifică demonul spațiului utilizator (sdcard) despre o nouă solicitare
  3. Daemonul spațiului utilizator citește /dev/fuse
  4. Daemonul spațiului utilizator analizează comanda și recunoaște operarea fișierului (ex. deschis)
  5. Demonul spațiului utilizator emite un apel de sistem către sistemul de fișiere real (EXT4)
  6. Kernel se ocupă de accesul fizic la date și trimite datele înapoi în spațiul utilizatorului
  7. Spațiul utilizator modifică (sau nu) datele și le transmite din nou prin /dev/fuse către kernel
  8. Kernel finalizează apelul de sistem original și mută datele în aplicația reală pentru spațiul utilizatorului (în exemplul nostru cat)

Asta pare mult de overhead doar la o singură comandă I/O care urmează să fie rulată. Și ai avea dreptate. Pentru a demonstra acest lucru, dl. Kowalczyk a încercat două teste I/O diferite: unul care implică copierea unui fișier mare și celălalt copierea multor fișiere mici. El a comparat viteza FUSE (pe partiția virtuală montată ca FAT32) care gestionează aceste operațiuni față de kernel (pe partiția de date formatată ca EXT4) și a descoperit că FUSE a contribuit într-adevăr în mod semnificativ deasupra capului.

În primul test, a copiat un fișier de 725 MB în ambele condiții de testare. El a descoperit că implementarea FUSE a transferat fișiere mari cu 17% mai încet.

În al doilea test, a copiat 10.000 de fișiere - fiecare dintre ele cu dimensiunea de 5 KB. În acest scenariu, implementarea FUSE sa încheiat Cu 40 de secunde mai lent pentru a copia practic 50 MB de date.

În lumea reală, acest hit de performanță afectează orice fișier stocat pe stocare externă. Aceasta înseamnă aplicații precum Maps care stochează fișiere mari pe /sdcard, aplicații de muzică care stochează tone de fișiere muzicale, aplicații pentru cameră și fotografii etc. Orice operațiune de I/O efectuată care implică stocarea externă este afectată de supraîncărcarea FUSE. Dar overhead I/O nu este singura problemă cu FUSE.

Problema #2 - Cache dublă

Memorarea în cache a datelor este importantă pentru îmbunătățirea performanței accesului la date. Stocând bucăți esențiale de date în memorie, nucleul Linux este capabil să reamintească rapid acele date atunci când este necesar. Dar datorită modului în care este implementat FUSE, Android stochează dublul cantității de cache necesară.

După cum demonstrează domnul Kowalczyk, se așteaptă ca un fișier de 10 MB să fie salvat în cache ca exact 10 MB, dar în schimb crește la dimensiunea cache. cu aproximativ 20 MB. Acest lucru este problematic pe dispozitivele cu mai puțină RAM, deoarece depozitele nucleului Linux utilizează cache-ul paginii pentru a stoca date în memorie. Dl. Kowalczyk a testat această problemă dublă de cache folosind această abordare:

  1. Creați un fișier cu o dimensiune cunoscută (pentru testare, 10 MB)
  2. Copiați-l în /sdcard
  3. Aruncați memoria cache a paginii
  4. Faceți un instantaneu al utilizării memoriei cache a paginii
  5. Citiți fișierul de testare
  6. Faceți un alt instantaneu al utilizării memoriei cache a paginii

Ceea ce a descoperit a fost că înainte de testul său, nucleul folosea 241 MB pentru cache-ul paginii. Odată ce și-a citit fișierul de testare, se aștepta să vadă 251 MB utilizați pentru cache-ul paginii. În schimb, a descoperit că acel nucleu folosea 263 MB pentru pagina cache - despre de două ori mai mult decât era de așteptat. Motivul pentru care se întâmplă acest lucru este că datele sunt stocate mai întâi în cache de aplicația utilizator care a emis inițial apelul I/O (FUSE) și, în al doilea rând, de demonul sdcard (EXT4 FS).

Problema #3 - Implementarea incompletă a FAT32

Există încă două probleme care decurg din utilizarea FUSE care emulează FAT32, care sunt mai puțin cunoscute în comunitatea Android.

Primul implică marcaje temporale incorecte. Dacă ați transferat vreodată un fișier (cum ar fi o fotografie) și ați observat că marcajul de timp este incorect, este din cauza implementării FUSE de către Android. Această problemă are existat pentru ani. Pentru a fi mai specific, problema implică utime() apel de sistem care vă permite să schimbați timpul de acces și modificare a unui fișier. Din păcate, apelurile efectuate către demonul sdcard ca utilizator standard nu au permisiunea corespunzătoare pentru a executa acest apel de sistem. Există soluții pentru acest lucru, dar ele vă cer să faceți acest lucru au acces root.

Dacă ați transferat vreodată un fișier (cum ar fi o fotografie) și ați observat că marcajul de timp este incorect, este din cauza implementării FUSE de către Android.

Următoarea problemă este mai îngrijorătoare pentru companiile care folosesc ceva de genul a card smartSD. Înainte de FUSE, producătorii de aplicații puteau monitoriza Steagul O_DIRECT pentru a comunica cu un microcontroler încorporat în card. Cu FUSE, dezvoltatorii pot accesa doar versiunea stocată în cache a unui fișier și nu pot vedea comenzile trimise de un microcontroler. Acest lucru este problematic pentru unele aplicații de întreprinderi/guvernamentale/bancare care comunică cu carduri microSD cu valoare adăugată.


Dumping FUSE pentru SDCardFS

Unii OEM-uri au recunoscut aceste probleme de la început și au început să caute o soluție în kernel pentru a înlocui FUSE. Samsung, de exemplu, a dezvoltat SDcardFS care se bazează pe WrapFS. Această soluție în kernel emulează FAT32 la fel ca FUSE, dar renunță la overhead I/O, dubla cache și alte probleme pe care le-am menționat mai sus. (Da, permiteți-mi să repet acest punct, această soluție pe care Google o implementează acum se bazează pe munca Samsung).

Google înșiși a recunoscut în sfârșit dezavantajele asociate cu FUSE, motiv pentru care au început să se îndrepte către stratul de emulare FAT32 în kernel dezvoltat de Samsung. Compania, așa cum se menționează în Dezvoltatorii Android în culise podcast, a lucrat pentru a face SDCardFS disponibil pentru toate dispozitivele într-o versiune viitoare a nucleului. În prezent, puteți vedea progresul lor lucrează în AOSP.

Ca Dezvoltatorul Google a explicat mai devreme, cea mai mare provocare cu implementarea unei soluții în kernel este cum să mapați numele pachetului ID-ul aplicației necesar pentru ca un pachet să-și acceseze propriile date în stocare externă fără a fi necesar permisiuni. Dar această declarație a fost făcută acum un an și am ajuns la punctul în care echipa numește SDCardFS „următorul lor lucru important”. Ei au confirmat deja că eroare temută de marcaj de timp a fost remediat, datorită îndepărtării de FUSE, așa că putem aștepta cu nerăbdare să vedem toate schimbările aduse odată cu abandonarea FUSE.


Concepții greșite de verificare a faptelor

Dacă ați ajuns până aici în articol, atunci felicitări pentru că ați ținut pasul cu totul până acum! Am vrut să clarific câteva întrebări pe care le-am avut eu când am scris acest articol:

  • SDcardFS are nimic de-a face cu cardurile SD reale. Este numit doar ca atare deoarece gestionează accesul I/O pentru /sdcard. Și după cum vă amintiți, /sdcard este o etichetă învechită care se referă la stocarea „externă” a dispozitivului dvs. (unde aplicațiile își stochează media).
  • SDcardFS este nu un sistem de fișiere tradițional cum ar fi FAT32, EXT4 sau F2FS. Este un sistem de fișiere stivuitor care transmite comenzi sistemelor de fișiere inferioare, emulate (în acest caz, ar fi FAT32 pe /sdcard).
  • Nu se va schimba nimic în ceea ce privește MTP. Veți continua să utilizați MTP pentru a transfera fișiere pe/de pe computerul dvs. (până când Google stabilește un protocol mai bun). Dar cel puțin eroarea de marcaj de timp va fi remediată!
  • După cum sa menționat anterior, atunci când Google se referă la „stocare externă” fie vorbește despre (în toate intențiile și scopuri) partiție internă/sdcard virtuală FAT32 SAU vorbesc despre un microSD real, fizic, amovibil card. Terminologia este confuză, dar este ceea ce ne lovește.

Concluzie

Îndepărtând de FUSE și implementând un strat de emulare FAT32 în kernel (SDCardFS), Google va reduce overhead I/O semnificativ, eliminând dubla stocare în cache și soluționând unele probleme obscure legate de emularea lui FUSE a FAT32.

Deoarece aceste modificări vor fi aduse unui nucleu, ele pot fi lansate fără o nouă versiune majoră de Android alături de acesta. Unii utilizatori se așteaptă să vadă aceste modificări implementate oficial în Android 8, dar este posibil pentru ca orice viitoare OTA pe un dispozitiv Pixel să aducă versiunea 4.1 a nucleului Linux la care Google a lucrat pe.

Pentru unii dintre voi, SDCardFS nu este un concept nou. De fapt, dispozitivele Samsung îl folosesc de ani de zile (au fost cei care l-au dezvoltat până la urmă). De când SDCardFS a fost introdus în AOSP anul trecut, unii dezvoltatori de ROM personalizate și kernel au ales să îl implementeze în munca lor. CyanogenMOD s-a gândit la un moment dat să-l implementeze, dar l-a anulat când utilizatorii au întâmpinat probleme cu fotografiile lor. Dar, sperăm, că Google preia conducerea acestui proiect, utilizatorii Android de pe toate dispozitivele viitoare vor putea profita de îmbunătățirile introduse odată cu abandonarea FUSE.