Задълбочено проучване на SDCardFS, заместителят на Google за FUSE, и как внедряването му ще намали режийните I/O разходи.
Преди няколко месеца Google добави нещо, наречено „SDCardFS” към официалните клонове на AOSP за ядрото на Linux. По това време движението беше забелязано само от някои разработчици на ядрото, но иначе мина под радара на повечето потребители. Това не е изненада, като се има предвид фактът, че повечето потребители, включително и аз, всъщност не знаят какво се случва под капака на операционната система Android и нейното ядро.
Въпреки това, последният епизод на Разработчици на Android зад кулисите подкаст поднови интереса към тази тема. Подкастът, организиран от Chet Haase (старши софтуерен инженер в Google), изследва скорошни и предстоящи промени, направени в ядрото. В шоуто беше разработчик на ядрото на Linux, работещ в екипа на Android - Rom Lemarchand. Дуото основно обсъди какви промени са направени, за да приспособят A/B актуализациите, но в последните 5 минути на епизода г-н Lemarchand говори за „следващото голямо нещо“, върху което екипът му работи – SDCardFS.
Трябва да призная, че научих за съществуването на SDCardFS, след като слушах този подкаст. Разбира се, не бях единственият, който се интересуваше от тази тема, като скорошна нишка в Reddit е показал. Въпреки това не бях доволен от основното обяснение, което беше предложено в подкаста и в опит да разсея някои от дезинформация, която се разпространява наоколо, направих собствено проучване и разговарях с няколко експерти със съответните познания по материя.
Огромни благодарности на софтуерния разработчик Михал Ковалчик за това, че допринесе със своите знания за тази статия и за това, че отдели време да отговори на въпросите ми.
„Външно“ е наистина вътрешно
Веднага ще има някои погрешни схващания, които трябва да изясним - в противен случай останалата част от статията ще бъде много объркваща. Полезно е да обсъдите историята на SD картите и телефоните с Android.
В ранните дни на телефоните с Android почти всяко устройство разчиташе на използването на техните microSD карти за съхранение. Това се дължи на факта, че телефоните по онова време се доставят с миниатюрен вътрешен капацитет за съхранение. Въпреки това, SD картите, използвани за съхраняване на приложения, често не осигуряват звездно потребителско изживяване, поне в сравнение със скоростта, с която вътрешната флаш памет може да чете/записва данни. Следователно нарастващото използване на SD карти за външно съхранение на данни се превърна в проблем за потребителското изживяване на Google.
Поради ранното разпространение на SD карти като външни устройства за съхранение, конвенциите за именуване на хранилищата на Android се основават на факта, че всяко устройство има действителен, физически слот за microSD карта. Но дори и на устройства, които не съдържат слот за SD карта, етикетът /sdcard все още се използва, за да посочи действителния чип за вътрешно съхранение. По-объркващ е фактът, че устройствата, които използват както физическа SD карта, така и чип за съхранение с голям капацитет, често назовават своите дялове на базата на SD картата. Например, в тези устройства точката на монтиране /sdcard ще се отнася до действителния вътрешен чип за съхранение, докато нещо като /storage/sdcard1 ще се отнася до физическата външна карта.
По този начин, въпреки че microSD картата на практика се счита за външно хранилище, конвенцията за именуване доведе до това, че „SDCard“ остава дълго след всяко действително използване на физическа карта. Това объркване със съхранението също създаде известно главоболие на разработчиците на приложения поради факта, че данните на приложението и неговите медии бяха разделени между двата дяла.
Малкото пространство за съхранение на ранните вътрешни чипове за съхранение доведе до това, че потребителите разочароващо откриха, че вече не могат да инсталират приложения (поради препълнения дял /data). Междувременно техните microSD карти с по-голям капацитет бяха преместени да държат само медии (като снимки, музика и филми). Потребителите, които разглеждаха нашите форуми навремето, може би си спомнят тези имена: Link2SD и Apps2SD. Това бяха (root) решения, които позволиха на потребителите да инсталират своите приложения и техните данни на физическата SD карта. Но това далеч не бяха перфектни решения, така че Google трябваше да се намеси.
Известно е, че Google изключи SD картите много рано. Nexus One остава единственото устройство Nexus със слот за microSD карта (и това ще бъде завинаги, тъй като марката Nexus на практика е мъртва). С Nexus S вече имаше само един унифициран дял за съхраняване на всички данни на приложенията и медиите - дялът /data. Това, което някога беше известно като /sdcard точка на монтиране, сега просто се отнасяше до виртуална файлова система (имплементирана под ПРЕДПАЗИТЕЛ протокол, както е обсъдено по-долу), разположен в дяла за данни - /data/media/0.
За да поддържа съвместимост и да намали объркването, Google все още използва този вече виртуален дял „sdcard“ за съхранение на медии. Но сега, когато този виртуален дял „sdcard“ всъщност се намира в /data, всичко, съхранено в него, ще се брои в пространството за съхранение на вътрешния чип за съхранение. По този начин производителите на оригинално оборудване трябваше да обмислят колко място да разпределят за приложения (/data) спрямо медии (/data/media).
Google се надяваше производителите да последват примера им и да се отърват от SD картите. За щастие, с течение на времето производителите на телефони успяха да доставят тези компоненти с по-висок капацитет, като същевременно останаха икономически ефективни, така че нуждата от SD карти започна да намалява. Но конвенциите за именуване се запазиха, за да намалят усилията, които разработчиците и производителите на оригинално оборудване трябва да положат, за да се приспособят. В момента, когато говорим за „външно хранилище“, имаме предвид едно от двете неща: действителната сменяема microSD карта или виртуалният дял „SDCard“, разположен в /data/media. Последният от тях, практически казано, всъщност е вътрешна памет, но конвенцията за именуване на Google го отличава поради факта, че тези данни са достъпни за потребителя (като например, когато е включен в компютъра).
В момента, когато говорим за „външно хранилище“, имаме предвид едно от двете неща: действителната сменяема microSD карта или виртуалният дял „SDCard“, разположен в /data/media.
Историята на виртуалните файлови системи на Android
Сега, когато „sdcard“ се третира като виртуална файлова система, това означава, че може да бъде форматирана като всяка файлова система, която Google иска. Започвайки с Nexus S и Android 2.3, Google избра да форматира „sdcard“ като VFAT (виртуален FAT). Този ход имаше смисъл по онова време, тъй като монтирането на VFAT би позволило на почти всеки компютър да има достъп до данните, съхранени на вашия телефон. Имаше обаче два основни проблема с това първоначално внедряване.
Първият засяга предимно крайния потребител (Вас). За да свържете вашето устройство към вашия компютър, ще използвате USB Mass Storage режим за прехвърляне на данни. Това обаче изисква устройството с Android да демонтира виртуалния дял, преди компютърът да има достъп до данните. Ако потребител иска да използва устройството си, докато е включено, много неща ще се показват като недостъпни.
The въвеждане на Media Transfer Protocol (MTP) реши този първи проблем. Когато е включен, вашият компютър вижда вашето устройство като устройство за „медийно съхранение“. Той изисква списък с файлове от вашия телефон и MTP връща списък с файлове, които компютърът може да изтегли от устройството. Когато даден файл бъде поискан да бъде изтрит, MTP изпраща команда за премахване на искания файл от хранилището. За разлика от USB Mass Storage Mode, който всъщност монтира „sdcard“, MTP позволява на потребителя да продължи да използва своето устройство, докато е включено. Освен това файловата система, присъстваща на телефона с Android, вече няма значение за разпознаването от компютъра на файловете на устройството.
Второ, съществуваше фактът, че VFAT не предоставя вида на стабилното управление на разрешенията, от което Google се нуждаеше. В началото много разработчици на приложения биха третирали „sdcard“ като сметище за данните на приложението си, без единно разбиране къде да съхраняват файловете си. Много приложения просто биха създали папка с името на приложението и съхранявали файловете си там.
Почти всяко приложение там по това време изискваше WRITE_EXTERNAL_STORAGE разрешение да записват файловете на приложението си във външното хранилище. По-тревожният обаче беше фактът, че почти всяко приложение също изискваше READ_EXTERNAL_STORAGE разрешение - само за четене на техните собствени файлове с данни! Това означаваше, че приложенията могат лесно да имат достъп до данни, съхранявани навсякъде във външното хранилище, и такова разрешение често се предоставя от потребителя, защото се изисква за много приложения, за да дори функция.
Google ясно видя това като проблематично. Цялата идея зад управлението на разрешенията е да се разделят приложенията, до които могат и не могат да имат достъп. Ако на почти всяко приложение се предоставя достъп за четене до потенциално чувствителни потребителски данни, тогава разрешението е безсмислено. Така Google решиха, че имат нужда от нов подход. Това е мястото, където идва FUSE.
Файлова система в потребителското пространство (FUSE)
Започвайки с Android 4.4, Google реши повече да не монтира виртуалния дял „sdcard“ като VFAT. Вместо това Google започна да използва FUSE, за да емулира FAT32 на виртуалния дял „sdcard“. С извикването на програмата sdcard FUSE за емулиране на разрешения за директория в стил FAT-on-sdcard, приложенията могат да започнат достъп до неговите данни, съхранявани във външно хранилище без да изисква никакви разрешения. Наистина, започвайки с ниво на API 19, READ_EXTERNAL_STORAGE вече не се изисква за достъп до файлове, намиращи се на външно хранилище - при условие, че папката с данни, създадена от демона FUSE, съответства на името на пакета на приложението. FUSE ще се справи синтезиране на собственика, групата и режимите на файлове на външно хранилище когато е инсталирано приложение.
FUSE се различава от модулите в ядрото, тъй като позволява на потребители без привилегии да пишат виртуални файлови системи. Причината, поради която Google внедри FUSE, е доста проста - той направи това, което искаха и вече беше добре разбран и документиран в света на Linux. Да цитирам а Разработчик на Google по въпроса:
„Тъй като FUSE е хубав стабилен API, по същество не е необходима никаква поддръжка при преминаване между версии на ядрото. Ако мигрираме към решение в ядрото, ще се регистрираме за поддържане на набор от корекции за всяка стабилна версия на ядрото.“ – Джеф Шарки, софтуерен инженер в Google
Въпреки това ставаше съвсем ясно, че режийните разходи на FUSE внасят удар в производителността наред с други проблеми. Разработчикът, с когото говорих по този въпрос, Михал Ковалчик, написа отлична публикация в блога преди повече от година с подробно описание на текущите проблеми с FUSE. Повече технически подробности можете да прочетете в неговия блог, но аз ще опиша откритията му (с негово разрешение) с по-прости думи.
Проблемът с FUSE
В Android демонът на потребителското пространство „sdcard“ използва FUSE, за да монтира /dev/fuse към емулираната външна директория за съхранение при зареждане. След това демонът на sdcard проверява устройството FUSE за чакащи съобщения от ядрото. Ако сте слушали подкаста, може би сте чули г-н Lemarchand да се позовава на FUSE, който въвежда режийни по време на I/O операции - ето какво се случва по същество.
В реалния свят този хит на производителността засяга всякакви файл, съхраняван на външна памет.
Проблем #1 - I/O Overhead
Да кажем, че създаваме прост текстов файл, наречен „test.txt“, и го съхраняваме в /sdcard/test.txt (което нека напомням ви, всъщност /data/media/0/test.txt приема, че текущият потребител е основният потребител на устройство). Ако искаме да прочетем (command cat) този файл, бихме очаквали системата да издаде 3 команди: отваряне, четене и затваряне. Наистина, както г-н Ковалчик демонстрира използването strace, това се случва:
Но тъй като файлът се намира във външното хранилище, което се управлява от sdcard daemon, има много допълнителни операции, които трябва да бъдат извършени. Според г-н Ковалчик, по същество са необходими 8 допълнителни стъпки всяка от тези 3 отделни команди:
- Приложението за потребителско пространство издава системно повикване, което ще се обработва от драйвера FUSE в ядрото (виждаме го в първия изход на strace)
- Драйверът FUSE в ядрото уведомява демона на потребителското пространство (sdcard) за нова заявка
- Демонът на потребителското пространство чете /dev/fuse
- Демонът на потребителското пространство анализира командата и разпознава файловата операция (напр. отворен)
- Демонът на потребителското пространство издава системно извикване към действителната файлова система (EXT4)
- Ядрото обработва физическия достъп до данни и изпраща данните обратно в потребителското пространство
- Потребителското пространство променя (или не) данните и ги предава отново през /dev/fuse към ядрото
- Ядрото завършва оригиналното системно извикване и премества данни към действителното потребителско приложение (в нашия пример cat)
Това изглежда като много режийни разходи само за една I/O команда, която трябва да бъде изпълнена. И ще бъдеш прав. За да демонстрира това, г-н Kowalczyk опита два различни I/O теста: единият включваше копиране на голям файл, а другият копира много малки файлове. Той сравни скоростта на FUSE (на виртуалния дял, монтиран като FAT32), обработващ тези операции спрямо ядрото (на дяла с данни, форматиран като EXT4), и той установи, че FUSE наистина допринася значително отгоре.
В първия тест той копира 725MB файл и при двете тестови условия. Той установи, че изпълнението на FUSE прехвърля големи файлове 17% по-бавно.
Във втория тест той копира 10 000 файла - всеки от тях с размер 5KB. В този сценарий изпълнението на FUSE приключи 40 секунди по-бавно да копирате основно данни на стойност 50 MB.
В реалния свят този хит на производителността засяга всякакви файл, съхраняван на външна памет. Това означава приложения като Карти, съхраняващи големи файлове на /sdcard, Музикални приложения, съхраняващи тонове музикални файлове, Камерни приложения и снимки и т.н. Всяка I/O операция, която се извършва, която включва външното хранилище, се влияе от режийните разходи на FUSE. Но I/O overhead не е единственият проблем с FUSE.
Проблем #2 - Двойно кеширане
Кеширането на данни е важно за подобряване на производителността на достъпа до данни. Като съхранява основни части от данни в паметта, ядрото на Linux е в състояние бързо да извика тези данни, когато е необходимо. Но поради начина, по който е внедрен FUSE, Android съхранява двойно количеството кеш, което е необходимо.
Както г-н Kowalczyk демонстрира, файл от 10 MB се очаква да бъде записан в кеша като точно 10 MB, но вместо това се увеличава до размера на кеша с около 20MBs. Това е проблематично на устройства с по-малко RAM, тъй като хранилището на ядрото на Linux използва кеша на страниците, за да съхранява данни в памет. Г-н Kowalczyk тества този проблем с двойното кеширане, използвайки този подход:
- Създайте файл с известен размер (за тестване, 10 MB)
- Копирайте го в /sdcard
- Пуснете кеша на страницата
- Направете моментна снимка на използването на кеша на страницата
- Прочетете тестовия файл
- Направете друга моментна снимка на използването на кеша на страницата
Това, което той установи, беше, че преди неговия тест 241 MB са били използвани от ядрото за кеш на страници. След като прочете своя тестов файл, той очакваше да види 251MBs, използвани за кеширане на страници. Вместо това той установи, че ядрото използва 263MBs за кеш на страници - около двойно повече от очакваното. Причината за това е, че данните първо се кешират от потребителското приложение, което първоначално е издало I/O повикването (FUSE), и второ от демона на sdcard (EXT4 FS).
Проблем #3 - Непълно внедряване на FAT32
Има още два проблема, произтичащи от използването на FUSE, емулиращ FAT32, които са по-малко известни в общността на Android.
Първият включва неправилни времеви отпечатъци. Ако някога сте прехвърляли файл (като снимка) и сте забелязали, че клеймото за време е неправилно, това се дължи на внедряването на FUSE в Android. Този проблем има съществуваше за години. За да бъдем по-конкретни, въпросът включва utime() системно повикване, което ви позволява да промените времето за достъп и модифициране на файл. За съжаление, повикванията, направени към демона на sdcard като стандартен потребител, нямат правилното разрешение за изпълнение на това системно повикване. Има заобиколни решения за това, но те изискват от вас имат root достъп.
Ако някога сте прехвърляли файл (като снимка) и сте забелязали, че клеймото за време е неправилно, това се дължи на внедряването на FUSE в Android.
Следващият проблем е по-загрижен за фирмите, използващи нещо като a smartSD карта. Преди FUSE създателите на приложения можеха да наблюдават O_DIRECT флаг за да комуникирате с вграден микроконтролер в картата. С FUSE разработчиците имат достъп само до кешираната версия на файл и не могат да видят никакви команди, изпратени от микроконтролер. Това е проблематично за някои корпоративни/правителствени/банкови приложения, които комуникират с microSD карти с добавена стойност.
Изхвърляне на FUSE за SDCardFS
Някои OMS разпознаха тези проблеми рано и започнаха да търсят решение в ядрото, което да замени FUSE. Samsung, например, разработи SDCardFS който е базиран на WrapFS. Това решение в ядрото емулира FAT32 точно както прави FUSE, но се отказва от входно-изходните разходи, двойното кеширане и други проблеми, които споменах по-горе. (Да, позволете ми да повторя тази точка, това решение, което Google сега прилага, се основава на работата на Samsung).
Самите Google най-накрая признаха недостатъците, свързани с FUSE, поради което започнаха да преминават към слоя за емулация на FAT32 в ядрото, разработен от Samsung. Компанията, както е посочено в Разработчици на Android зад кулисите podcast, работи върху предоставянето на SDCardFS за всички устройства в предстояща версия на ядрото. В момента можете да видите напредъка им работа в AOSP.
Като Разработчикът на Google обясни по-рано, най-голямото предизвикателство при внедряването на решение в ядрото е как да съпоставите името на пакета към идентификатор на приложение, необходим на пакета за достъп до собствените си данни във външно хранилище, без да се изисква такова разрешения. Но това изявление беше направено преди година и стигнахме до момента, в който екипът нарича SDCardFS своето „следващото голямо нещо“. Те вече потвърдиха, че страховита грешка в клеймото за време е коригиран, благодарение на отдалечаването от FUSE, така че можем да очакваме с нетърпение да видим всички промени, произтичащи от изоставянето на FUSE.
Погрешни схващания при проверка на факти
Ако сте стигнали дотук в статията, тогава браво, че сте в крак с всичко досега! Исках да изясня няколко въпроса, които имах, когато писах тази статия:
- SDCardFS има нищо общо с действителните SD карти. Просто е наречен така, защото обработва I/O достъп за /sdcard. И както може би си спомняте, /sdcard е остарял етикет, отнасящ се до „външното“ хранилище на вашето устройство (където приложенията съхраняват своите медии).
- SDCardFS е не е традиционна файлова система като FAT32, EXT4 или F2FS. Това е стекируема обвиваща файлова система, която предава команди към по-ниските, емулирани файлови системи (в този случай това ще бъде FAT32 на /sdcard).
- Нищо няма да се промени по отношение на MTP. Ще продължите да използвате MTP за прехвърляне на файлове към/от вашия компютър (докато Google се спре на по-добър протокол). Но поне грешката в клеймото за време ще бъде коригирана!
- Както споменахме по-рано, когато Google говори за „Външно хранилище“, те или говорят за (за всички намерения и цели) вътрешен /sdcard виртуален FAT32 дял ИЛИ те говорят за действителна, физическа, сменяема microSD карта. Терминологията е объркваща, но това е, от което сме поразени.
Заключение
Като се отдалечи от FUSE и внедри слой за емулация на FAT32 в ядрото (SDCardFS), Google ще намали значителни входно/изходни разходи, елиминиране на двойното кеширане и решаване на някои неясни проблеми, свързани с емулацията на FUSE на FAT32.
Тъй като тези промени ще бъдат направени в ядрото, те могат да бъдат пуснати без голяма нова версия на Android заедно с него. Някои потребители очакват да видят тези промени официално внедрени в Android 8, но е възможно за всяко бъдещо OTA на устройство Pixel, за да донесе ядрото на Linux версия 4.1, което Google работи На.
За някои от вас SDCardFS не е нова концепция. Всъщност устройствата на Samsung го използват от години (все пак те бяха тези, които го разработиха). Откакто SDCardFS беше въведен в AOSP миналата година, някои персонализирани разработчици на ROM и ядра избраха да го внедрят в работата си. CyanogenMOD в един момент обмисляше да го внедри, но го върна, когато потребителите срещнаха проблеми с техните снимки. Но се надяваме, че Google поема управлението на този проект, потребителите на Android на всички бъдещи устройства могат да се възползват от подобренията, въведени с изоставянето на FUSE.