Merülés az SDCardFS-be: Hogyan csökkenti a Google BIZTOSÍTÉK cseréje az I/O többletköltséget

Az SDCardFS, a Google által a FUSE-t helyettesítő SDCardFS alapos feltárása, és annak megvalósítása, hogy hogyan csökkenti az I/O többletköltséget.

Néhány hónappal ezelőtt a Google hozzáadott egy „SDCardFS” a hivatalos AOSP ágakba a Linux kernelhez. Akkoriban a lépést csak az vette észre néhány kernelfejlesztő, de egyébként a legtöbb felhasználó radarja alatt repült. Nem meglepő, ha figyelembe vesszük azt a tényt, hogy a legtöbb felhasználó, beleértve én is, nem igazán tudja, mi történik az Android operációs rendszer és kernelének burkolata alatt.

Azonban a legutóbbi epizód a Android Developers Backstage A podcast újra érdeklődött a téma iránt. A Chet Haase (a Google vezető szoftvermérnöke) által üzemeltetett podcast a kernelen végrehajtott közelmúltbeli és közelgő változtatásokat vizsgálta. A műsorban volt egy Linux kernel fejlesztő, aki az Android csapatán dolgozott, Rom Lemarchand. A páros elsősorban arról beszélt, hogy milyen változtatásokat hajtottak végre az A/B frissítések érdekében, de az epizód utolsó 5 percében Mr. Lemarchand a „következő nagy dologról” beszélt, amin csapata dolgozik –

SDCardFS.

Be kell vallanom, hogy a podcast meghallgatása után tudtam meg az SDCardFS létezéséről. Természetesen nem én voltam az egyetlen, akit ez a téma érdekelt, hiszen a legutóbbi Reddit szál mutatta. Azonban nem voltam megelégedve a podcastban felkínált alapvető magyarázattal, és nem voltam hajlandó eloszlatni néhány félretájékoztatás terjedt el, saját kutatásokat végeztem, és beszéltem néhány olyan szakértővel, akik releváns ismeretekkel rendelkeznek a témában ügy.

Külön köszönet illeti Michal Kowalczyk szoftverfejlesztőt, hogy tudásával hozzájárult ehhez a cikkhez, és hogy időt szakított kérdéseim megválaszolására.


A „külső” valóban belső

Rögtön minden bizonnyal van néhány tévhit, amelyet tisztáznunk kell – különben a cikk többi része nagyon zavaró lesz. Hasznos megvitatni az SD-kártyák és az Android telefonok történetét.

Az Android telefonok korai napjaiban szinte minden eszköz a microSD-kártyák tárolására támaszkodott. Ez annak volt köszönhető, hogy a telefonok akkoriban csekély belső tárolókapacitással szállították. Az alkalmazások tárolására használt SD-kártyák azonban gyakran nem nyújtanak kiemelkedő felhasználói élményt, legalábbis ahhoz a sebességhez képest, amellyel a belső flash memória képes olvasni/írni az adatokat. Emiatt az SD-kártyák külső adattárolásra való növekvő használata a felhasználói élmény aggodalmává vált a Google számára.

Az SD-kártyák külső tárolóeszközként való korai elterjedése miatt az Android tárolási elnevezési konvenciói azon a tényen alapultak, hogy minden eszköznek volt tényleges, fizikai microSD-kártyanyílása. De még azokon az eszközökön is, amelyek nem tartalmaztak SD-kártyanyílást, az /sdcard címkét továbbra is a tényleges belső tárolóchipre mutatták. Még zavaróbb az a tény, hogy azok az eszközök, amelyek mind fizikai SD-kártyát, mind nagy kapacitású tárolóchipet használnak tárolásra, gyakran az SD-kártya alapján nevezik el partícióikat. Például ezekben az eszközökben az /sdcard beillesztési pont a tényleges belső tároló chipre, míg a /storage/sdcard1 a fizikai külső kártyára utal.

Így annak ellenére, hogy a microSD-kártyát gyakorlatilag külső tárolónak tekintik, az elnevezési konvenció azt eredményezte, hogy az „SDCard” már régen megmaradt a fizikai kártya tényleges használata után. A tárolással való összekeveredés némi fejtörést is okozott az alkalmazásfejlesztőknek, mivel az alkalmazásadatok és azok adathordozói elkülönítve voltak a két partíció között.

A korai belső tárolóchipek alacsony tárhelye miatt a felhasználók frusztrálóan rájöttek, hogy már nem tudnak alkalmazásokat telepíteni (mivel a /data partíció megtelt). Időközben a nagyobb kapacitású microSD-kártyáik csak adathordozókat (például fényképeket, zenét és filmeket) tároltak. A fórumainkat régebben böngésző felhasználók emlékezhetnek a következő nevekre: Link2SD és Apps2SD. Ezek olyan (root)megoldások voltak, amelyek lehetővé tették a felhasználók számára, hogy alkalmazásaikat és adataikat a fizikai SD-kártyára telepítsék. De ezek korántsem voltak tökéletes megoldások, így a Google-nak kellett lépnie.

Híres, hogy a Google már nagyon korán kihúzta az SD-kártyákat. A Nexus One továbbra is az egyetlen Nexus eszköz, amely microSD-kártyanyílással rendelkezik (és örökre az is marad, mivel a Nexus márka gyakorlatilag halott). A Nexus S-ben már csak egyetlen, egységes partíció volt az összes alkalmazásadat és adathordozó tárolására – a /data partíció. Amit egykor /sdcard csatolási pontként ismertek, most egyszerűen egy virtuális fájlrendszerre utalt (amely a BIZTOSÍTÉK az alább tárgyalt protokoll) az adatpartícióban található - /data/media/0.

A kompatibilitás fenntartása és a félreértések csökkentése érdekében a Google továbbra is ezt a virtuális „sdcard” partíciót használta a média tárolására. De most, hogy ez az „sdcard” virtuális partíció ténylegesen a /data-ban található, a benne tárolt dolgok beleszámítanak a belső tárolóchip tárhelyébe. Így az OEM-nek kellett mérlegelnie, hogy mennyi helyet kell lefoglalni az alkalmazásoknak (/data) a médiához (/data/media) képest.

Két nagyon különböző "SD kártya"

A Google azt remélte, hogy a gyártók követik a példájukat, és megszabadulnak az SD-kártyáktól. Szerencsére az idő múlásával a telefongyártók nagyobb kapacitással tudták beszerezni ezeket az alkatrészeket, miközben költséghatékonyak maradtak, így az SD-kártyák iránti igény kezdett elfogyni. De az elnevezési konvenciók továbbra is fennálltak annak érdekében, hogy csökkentsék a fejlesztőknek és az OEM-eknek az alkalmazkodáshoz szükséges erőfeszítéseit. Jelenleg, amikor „külső tárhelyre” utalunk, erre utalunk két dolog egyike: a tényleges cserélhető microSD-kártya vagy a /data/media mappában található virtuális „SDCard” partíció. Ezek közül az utóbbi gyakorlatilag valójában belső tárhely, de a Google elnevezési konvenciója megkülönbözteti, mivel ezek az adatok elérhetők a felhasználó számára (például a számítógéphez csatlakoztatva).

Jelenleg, amikor „külső tárhelyre” utalunk, erre utalunk két dolog egyike: a tényleges cserélhető microSD-kártya vagy a /data/media mappában található virtuális „SDCard” partíció.


Az Android virtuális fájlrendszereinek története

Most, hogy az „sdcard”-t virtuális fájlrendszerként kezelik, ez azt jelentette, hogy a Google által kívánt bármely fájlrendszerre formázható. A Nexus S-től és az Android 2.3-tól kezdve a Google az „sdcard” VFAT-ként (virtuális FAT) történő formázását választotta. Ennek a lépésnek akkoriban volt értelme, mivel a VFAT felszerelése lehetővé tette, hogy szinte minden számítógép hozzáférjen a telefonon tárolt adatokhoz. Ezzel a kezdeti megvalósítással azonban két fő probléma volt.

Az első elsősorban a végfelhasználóra (Önre) vonatkozik. Ahhoz, hogy eszközét a számítógéphez csatlakoztassa, USB háttértár módot kell használnia az adatok átviteléhez. Ehhez azonban az Android-eszköznek le kellett választania a virtuális partíciót, mielőtt a számítógép hozzáférhetett volna az adatokhoz. Ha egy felhasználó csatlakoztatva akarná használni az eszközét, sok dolog elérhetetlenként jelenik meg.

A a Media Transfer Protocol bevezetése (MTP) megoldotta ezt az első problémát. Ha csatlakoztatva van, a számítógép „médiatároló” eszköznek tekinti az eszközt. Fájllistát kér a telefontól, az MTP pedig visszaadja azon fájlok listáját, amelyeket a számítógép letölthet az eszközről. Amikor egy fájl törlését kérik, az MTP parancsot küld a kért fájl tárolóból való eltávolítására. Ellentétben az USB Mass Storage móddal, amely valójában az „sdcardot” rögzíti, az MTP lehetővé teszi a felhasználó számára, hogy továbbra is használja eszközét, miközben csatlakoztatva van. Ezenkívül az Android-telefonon található fájlrendszer már nem számít ahhoz, hogy a számítógép felismerje az eszközön lévő fájlokat.

Másodszor, ott volt az a tény, hogy a VFAT nem biztosította azt a fajta robusztus engedélykezelést, amelyre a Google-nak szüksége volt. Kezdetben sok alkalmazásfejlesztő az „sdcard”-ot az alkalmazás adatainak lerakóhelyeként kezelte, és nem volt egységes elképzelése arról, hogy hol tárolja fájljait. Sok alkalmazás egyszerűen létrehoz egy mappát az alkalmazás nevével, és ott tárolja a fájljait.

Akkoriban szinte minden alkalmazás megkövetelte a WRITE_EXTERNAL_STORAGE engedélyt arra, hogy alkalmazásfájljaikat a külső tárolóra írják. A nyugtalanító azonban az volt, hogy szinte minden alkalmazáshoz szükséges volt a READ_EXTERNAL_STORAGE engedély - csak a saját adatfájlok olvasásához! Ez azt jelentette, hogy az alkalmazások könnyen hozzáférhettek a külső tárolón bárhol tárolt adatokhoz, és ezt az engedélyt gyakran a felhasználó adta meg, mert sok alkalmazásnak szüksége volt rá funkció.

A Google ezt egyértelműen problémásnak látta. Az engedélykezelés mögött meghúzódó alapelv az, hogy elkülönítsék, hogy az alkalmazások melyhez férhetnek hozzá és melyekhez nem. Ha szinte minden alkalmazás olvasási hozzáférést kap a potenciálisan érzékeny felhasználói adatokhoz, akkor az engedély értelmetlen. Így a Google úgy döntött, hogy új megközelítésre van szükségük. Itt jön be a FUSE.


Fájlrendszer a felhasználói térben (FUSE)

Az Android 4.4-től kezdődően a Google úgy döntött, hogy a továbbiakban nem csatlakoztatja a virtuális „sdcard” partíciót VFAT-ként. Ehelyett a Google FUSE-t kezdett használni a FAT32 emulálására az „sdcard” virtuális partíción. Az sdcard program hívásával FUSE a FAT-on-sdcard stílusú könyvtárengedélyek emulálásához, az alkalmazások hozzáférhetnek a külső tárolón tárolt adataihoz engedélyek nélkül. Valójában a 19. API-szinttől kezdve a READ_EXTERNAL_STORAGE-nak már nem volt szüksége a megtalált fájlokhoz külső tárolón – feltéve, hogy a FUSE démon által létrehozott adatmappa megegyezik az alkalmazás csomagnevével. A FUSE kezelné a fájlok tulajdonosának, csoportjának és módozatainak szintetizálása a külső tárolón amikor egy alkalmazás telepítve van.

A FUSE különbözik a kernelen belüli moduloktól, mivel lehetővé teszi a nem privilegizált felhasználók számára virtuális fájlrendszerek írását. Az ok, amiért a Google bevezette a FUSE-t, meglehetősen egyszerű – azt tette, amit akartak, és már meg is volt jól érthető és dokumentált a Linux világában. Hogy idézzem a Google fejlesztő az ügyben:

"Mivel a FUSE egy szép stabil API, gyakorlatilag nincs szükség karbantartási munkára a kernelverziók közötti váltáskor. Ha áttérnénk egy kernelen belüli megoldásra, akkor minden stabil kernelverzióhoz egy javítási készletet karbantartanánk." -Jeff Sharkey, a Google szoftvermérnöke

Azonban teljesen világossá vált, hogy a FUSE rezsije többek között a teljesítményben is ütést hozott. A fejlesztő, akivel az üggyel kapcsolatban beszéltem, Michal Kowalczyk, remek blogbejegyzést írt több mint egy évvel ezelőtt részletezi a FUSE aktuális problémáit. További technikai részletek a blogján olvashatók, de megállapításait (engedélyével) laikusabban írom le.


A probléma a FUSE-val

Az Android rendszerben az „sdcard” userspace démon a FUSE-t használja a /dev/fuse csatolásához az emulált külső tárolókönyvtárhoz rendszerindításkor. Ezt követően az sdcard démon lekérdezi a FUSE eszközt a kerneltől érkező függőben lévő üzenetek után. Ha hallgatta a podcastot, hallhatta, hogy Mr. Lemarchand utalt arra, hogy a FUSE bevezeti a többletterhelést az I/O műveletek során – lényegében ez történik.

A való világban ez a teljesítménysláger hatással van Bármi külső tárhelyen tárolt fájl.

1. probléma – I/O overhead

Tegyük fel, hogy létrehozunk egy egyszerű szöveges fájlt, a „teszt.txt” nevet, és eltároljuk a /sdcard/test.txt fájlban (ez pedig legyen emlékeztetlek, valójában a /data/media/0/test.txt, feltételezve, hogy az aktuális felhasználó az elsődleges felhasználó eszköz). Ha ezt a fájlt el akarjuk olvasni (command cat), akkor azt várnánk, hogy a rendszer 3 parancsot adjon ki: nyitás, olvasás, majd bezárás. Valóban, ahogy Kowalczyk úr bemutatja a használatát strace, ez történik:

De mivel a fájl az sdcard démon által kezelt külső tárolón található, sok további műveletet kell végrehajtani. Kowalczyk úr szerint lényegében 8 további lépésre van szükség mind a 3 egyedi parancs:

  1. A Userspace alkalmazás rendszerhívást ad ki, amelyet a kernel FUSE illesztőprogramja kezel (ezt az első strace kimenetben látjuk)
  2. A kernelben lévő FUSE illesztőprogram értesíti a userspace démont (sdcard) az új kérésről
  3. A Userspace démon beolvassa a /dev/fuse fájlt
  4. A Userspace démon elemzi a parancsot, és felismeri a fájlműveleteket (pl. nyisd ki)
  5. A Userspace démon rendszerhívást ad ki a tényleges fájlrendszernek (EXT4)
  6. A kernel kezeli a fizikai adathozzáférést, és visszaküldi az adatokat a felhasználói térbe
  7. A Userspace módosítja (vagy nem) az adatokat, és újra átadja a /dev/fuse-n keresztül a kernelnek
  8. A kernel befejezi az eredeti rendszerhívást, és áthelyezi az adatokat a tényleges userspace alkalmazásba (a példánkban a cat)

Ez úgy tűnik nagyon csak egyetlen futtatandó I/O parancsra. És igazad lenne. Ennek demonstrálására Kowalczyk úr két különböző I/O tesztet kísérelt meg: az egyikben egy nagy fájl másolását, a másikban pedig sok kis fájl másolását. Összehasonlította az ezeket a műveleteket kezelő FUSE sebességét (a FAT32-ként szerelt virtuális partíción) a kernel (az EXT4 formátumú adatpartíción), és úgy találta, hogy a FUSE valóban jelentős mértékben hozzájárul felső.

Az első tesztben egy 725 MB-os fájlt másolt mindkét tesztkörülmények között. Megállapította, hogy a FUSE megvalósítása nagy fájlokat vitt át 17%-kal lassabban.

A második tesztben 10 000 fájlt másolt le – mindegyik 5 KB méretű. Ebben a forgatókönyvben a FUSE megvalósítása véget ért 40 másodperccel lassabb alapvetően 50 MB értékű adat másolásához.

A való világban ez a teljesítménysláger hatással van Bármi külső tárhelyen tárolt fájl. Ez azt jelenti, hogy az olyan alkalmazások, mint a Térképek, amelyek nagy fájlokat tárolnak az /sdcard kártyán, a zenei alkalmazások, amelyek rengeteg zenei fájlt tárolnak, a fényképezőgép-alkalmazások és fényképek stb. Minden olyan I/O műveletet, amely a külső tárolót érinti, a FUSE többletterhelése befolyásolja. De nem az I/O overhead az egyetlen probléma a FUSE-val.

2. probléma – Dupla gyorsítótár

Az adatok gyorsítótárazása fontos az adathozzáférési teljesítmény javítása szempontjából. Azáltal, hogy az alapvető adatokat a memóriában tárolja, a Linux kernel szükség esetén gyorsan elő tudja hívni ezeket az adatokat. De a FUSE megvalósításának módja miatt az Android a szükséges gyorsítótár dupláját tárolja.

Amint azt Kowalczyk úr bemutatja, egy 10 MB-os fájlt várhatóan pontosan 10 MB-ként kell elmenteni a gyorsítótárba, de ehelyett a gyorsítótár méretét növelik. körülbelül 20 MB-tal. Ez problémás a kevesebb RAM-mal rendelkező eszközökön, mivel a Linux kerneltárolók az oldalgyorsítótárat használják az adatok tárolására memória. Kowalczyk úr a következő módszerrel tesztelte ezt a kettős gyorsítótárazási problémát:

  1. Ismert méretű fájl létrehozása (tesztelés céljából 10 MB)
  2. Másold át az /sdcard mappába
  3. Dobja el az oldal gyorsítótárát
  4. Készítsen pillanatképet az oldal gyorsítótárának használatáról
  5. Olvassa el a tesztfájlt
  6. Készítsen még egy pillanatképet az oldal gyorsítótárának használatáról

Azt találta, hogy a tesztje előtt a kernel 241 MB-ot használt az oldal gyorsítótárára. Miután elolvasta a tesztfájlt, 251 MB-ot várt az oldal gyorsítótárára. Ehelyett azt találta, hogy a kernel használja 263 MB oldal gyorsítótárához – kb kétszerese a vártnak. Ennek az az oka, hogy az adatokat először az I/O hívást eredetileg kibocsátó felhasználói alkalmazás (FUSE), másodszor pedig az sdcard démon (EXT4 FS) gyorsítótárazza.

3. probléma - A FAT32 nem teljes megvalósítása

A FAT32-t emuláló FUSE használatából fakad még két probléma, amelyek kevésbé ismertek az Android közösségében.

Az első magában foglalja helytelen időbélyegek. Ha valaha is átvitt egy fájlt (például egy fényképet), és észrevette, hogy az időbélyeg hibás, az a FUSE Android implementációja miatt van. Ez a probléma számára létezett évek. Konkrétabban szólva a probléma magában foglalja a utime() rendszerhívás, amely lehetővé teszi a fájl hozzáférési és módosítási idejének módosítását. Sajnos az sdcard démonnak mint normál felhasználónak indított hívások nem rendelkeznek megfelelő engedéllyel a rendszerhívás végrehajtásához. Vannak erre megoldások, de ezek megkövetelik Önt root hozzáféréssel rendelkezik.

Ha valaha is átvitt egy fájlt (például egy fényképet), és észrevette, hogy az időbélyeg hibás, az a FUSE Android implementációja miatt van.

A következő probléma inkább az olyan vállalkozásokat érinti, amelyek a smartSD kártya. A FUSE előtt az alkalmazásgyártók figyelemmel kísérhették a O_DIRECT jelző a kártyába beágyazott mikrokontrollerrel való kommunikáció érdekében. A FUSE segítségével a fejlesztők csak a fájl gyorsítótárazott verziójához férhetnek hozzá, és nem láthatják a mikrovezérlők által küldött parancsokat. Ez problémás egyes vállalati/kormányzati/banki alkalmazásoknál, amelyek értéknövelt microSD-kártyákkal kommunikálnak.


Dömping BIZTOSÍTÉK az SDCardFS-hez

Egyes eredeti gyártók már korán felismerték ezeket a problémákat, és kernelen belüli megoldást kezdtek keresni a FUSE helyettesítésére. A Samsung például kifejlesztett SDCardFS amely a WrapFS-en alapul. Ez a kernelen belüli megoldás ugyanúgy emulálja a FAT32-t, mint a FUSE, de elkerüli az I/O többletterhelést, a dupla gyorsítótárat és a fent említett egyéb problémákat. (Igen, hadd ismételjem meg, ez a Google által most megvalósított megoldás a Samsung munkáján alapul).

A Google végre elismerte a FUSE-hoz kapcsolódó hátrányokat, ezért elkezdtek a Samsung által fejlesztett kernelen belüli FAT32 emulációs réteg felé haladni. A cég, amint azt a Android Developers Backstage podcast, azon dolgozik, hogy az SDCardFS elérhető legyen minden eszköz számára a kernel közelgő verziójában. Jelenleg láthatja a fejlődésüket dolgozik az AOSP-ben.

Mint a A Google fejlesztője korábban kifejtette, a kernelen belüli megoldás megvalósításának legnagyobb kihívása a csomagnév leképezése alkalmazásazonosító szükséges ahhoz, hogy egy csomag hozzáférjen a saját adataihoz a külső tárolóban anélkül, hogy erre szükség lenne engedélyeket. De ez a kijelentés egy éve hangzott el, és elértük azt a pontot, ahol a csapat az SDCardFS-t a „következő nagy dolognak” nevezi. Már megerősítették, hogy a rettegett időbélyeg hiba javítva lett, köszönhetően a FUSE-tól való eltávolodásnak, így már alig várjuk, hogy láthassuk a FUSE elhagyásával járó összes változást.


Tényellenőrző tévhitek

Ha idáig eljutottál a cikkig, akkor köszönet, hogy lépést tartottál eddig mindennel! Szeretnék tisztázni néhány kérdést, ami a cikk írásakor felmerült bennem:

  • Az SDCardFS rendelkezik semmi köze a valódi SD-kártyákhoz. Csak azért nevezték el, mert kezeli az /sdcard I/O hozzáférését. És amint emlékszik, az /sdcard egy elavult címke, amely az eszköz „külső” tárhelyére utal (ahol az alkalmazások tárolják a médiatartalmukat).
  • Az SDCardFS az nem hagyományos fájlrendszer mint a FAT32, EXT4 vagy F2FS. Ez egy egymásra rakható burkoló fájlrendszer, amely parancsokat ad át az alacsonyabb szintű, emulált fájlrendszereknek (ebben az esetben az /sdcard FAT32-je lenne).
  • Semmi sem fog változni az MTP-vel kapcsolatban. Továbbra is az MTP-t fogja használni a fájlok számítógépre/számítógépről való átvitelére (amíg a Google jobb protokollt választ). De legalább az időbélyeg hiba javítva lesz!
  • Ahogy korábban említettük, amikor a Google a "külső tárhelyre" hivatkozik, akkor vagy a (minden célból és célokra) belső /sdcard virtuális FAT32 partíció VAGY tényleges, fizikai, cserélhető microSD-ről beszélnek kártya. A terminológia zavaros, de ez az, amitől megdöbbentünk.

Következtetés

A FUSE elhagyásával és a kernelen belüli FAT32 emulációs réteg (SDCardFS) bevezetésével a Google csökkenti jelentős I/O többletterhelés, kiküszöböli a dupla gyorsítótárat, és megold néhány, a FUSE emulációjával kapcsolatos homályos problémát. FAT32.

Mivel ezek a változtatások egy kernelen fognak végrehajtódni, bevezethetők anélkül, hogy mellé kerülne az Android jelentős új verziója. Egyes felhasználók arra számítanak, hogy ezeket a változtatásokat hivatalosan is végrehajtják az Android 8-ban, de ez lehetséges minden jövőbeni OTA Pixel eszközön, hogy a Linux kernel 4.1-es verzióját hozza, amelyen a Google dolgozott tovább.

Egyesek számára az SDCardFS nem új fogalom. Valójában a Samsung készülékek évek óta használják (végül is ők fejlesztették ki). Amióta tavaly bevezették az SDCardFS-t az AOSP-ben, néhány egyéni ROM- és kernelfejlesztő úgy döntött, hogy beépíti a munkájába. A CyanogenMOD egy ponton fontolóra vette a megvalósítását, de visszavonta, amikor a felhasználók problémába ütköztek a fotóikkal. De remélhetőleg miután a Google átveszi az uralmat ebben a projektben, az Android-felhasználók minden jövőbeni eszközön kihasználhatják a FUSE elhagyásával bevezetett fejlesztéseket.