Поглиблене дослідження SDCardFS, заміни Google для FUSE, і того, як її впровадження зменшить витрати на введення-виведення.
Кілька місяців тому Google додав щось під назвою "SDCardFS” до офіційних гілок AOSP для ядра Linux. На той момент переїзд помітили лише деякі розробники ядра, але в іншому випадку пройшов поза увагою більшості користувачів. Це не дивно, враховуючи той факт, що більшість користувачів, включаючи мене, насправді не знають, що відбувається під капотом ОС Android та її ядра.
Однак останній епізод Розробники Android Backstage подкаст відновив інтерес до цієї теми. Подкаст, організований Четом Хаасе (старшим інженером-програмістом у Google), досліджував останні та майбутні зміни, внесені до ядра. На шоу був розробник ядра Linux, який працює в команді Android - Ром Лемаршан. Дует насамперед обговорював, які зміни було внесено для оновлення A/B, але в останні 5 хвилин епізоду пан Лемаршан говорив про «наступну велику справу», над якою працює його команда – 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. Це були (кореневі) рішення, які дозволяли користувачам встановлювати свої програми та їх дані на фізичну SD-карту. Але це були далеко не ідеальні рішення, тому Google довелося втрутитися.
Відомо, що Google дуже рано відмовився від SD-карт. Nexus One залишається єдиним пристроєм Nexus із слотом для карт пам’яті microSD (і так буде назавжди, оскільки бренд Nexus фактично припинив свою діяльність). З Nexus S тепер існував лише один уніфікований розділ для зберігання всіх даних програм і носіїв – розділ /data. Те, що колись було відомо як точка монтування /sdcard, тепер просто посилалося на віртуальну файлову систему (реалізовану під ЗАПОБІЖНИК протокол, як описано нижче), розташований у розділі даних - /data/media/0.
Щоб зберегти сумісність і зменшити плутанину, Google все ще використовував цей тепер уже віртуальний розділ «sdcard» для зберігання медіафайлів. Але тепер, коли цей віртуальний розділ «sdcard» фактично розташований у /data, все, що зберігається в ньому, зараховуватиметься до простору для зберігання внутрішньої мікросхеми пам’яті. Таким чином, OEM-виробники повинні були розглянути, скільки місця виділити для програм (/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 для передачі даних. Однак для цього пристрій Android мав відключити віртуальний розділ, перш ніж комп’ютер зможе отримати доступ до даних. Якби користувач хотів використовувати свій пристрій підключеним до мережі, багато речей відображалися б як недоступні.
The запровадження протоколу передачі медіа (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 щодо будь-яких повідомлень, що очікують від ядра. Якщо ви слухали подкаст, то, напевно, чули, як пан Лемаршан згадував про те, що FUSE вводить накладні витрати під час операцій вводу-виводу – ось що, по суті, відбувається.
У реальному світі це зниження продуктивності впливає будь-який файл, збережений на зовнішній пам’яті.
Проблема №1 - Накладні витрати на введення/виведення
Скажімо, ми створюємо простий текстовий файл під назвою «test.txt» і зберігаємо його в /sdcard/test.txt (що дозволяє я нагадую вам, насправді /data/media/0/test.txt припускає, що поточний користувач є основним користувачем на пристрій). Якби ми хотіли прочитати (команда cat) цей файл, ми очікували б, що система видасть 3 команди: відкрити, прочитати, потім закрити. Дійсно, як показує пан Ковальчик використання strace, ось що відбувається:
Але оскільки файл знаходиться на зовнішній пам’яті, якою керує демон sdcard, потрібно виконати багато додаткових операцій. За словами пана Ковальчика, по суті, необхідно зробити 8 додаткових кроків кожну з цих 3 окремих команд:
- Програма Userspace видає системний виклик, який оброблятиметься драйвером FUSE в ядрі (ми бачимо це у першому виведенні strace)
- Драйвер FUSE в ядрі повідомляє демон простору користувача (sdcard) про новий запит
- Демон простору користувача читає /dev/fuse
- Демон простору користувача аналізує команду та розпізнає операції з файлами (наприклад, ВІДЧИНЕНО)
- Демон простору користувача надсилає системний виклик фактичній файловій системі (EXT4)
- Ядро обробляє фізичний доступ до даних і надсилає дані назад у простір користувача
- Простір користувача змінює (чи ні) дані та знову передає їх через /dev/fuse до ядра
- Ядро завершує початковий системний виклик і переміщує дані до фактичної програми користувача (у нашому прикладі cat)
Це здається багато накладних витрат лише на одну команду введення-виведення, яку потрібно виконати. І ти мав би рацію. Щоб продемонструвати це, пан Ковальчик спробував провести два різні тести вводу-виводу: один із копіюванням великого файлу, а інший — з копіюванням великої кількості маленьких файлів. Він порівняв швидкість обробки цих операцій FUSE (на віртуальному розділі, змонтованому як FAT32) із ядра (у розділі даних, відформатованому як EXT4), і він виявив, що FUSE справді значний внесок накладні витрати.
У першому тесті він скопіював файл розміром 725 МБ за обох умов тестування. Він виявив, що реалізація FUSE передає великі файли на 17% повільніше.
У другому тесті він скопіював 10 000 файлів, кожен із яких розміром 5 Кб. У цьому сценарії впровадження FUSE завершилося на 40 секунд повільніше скопіювати в основному 50 Мб даних.
У реальному світі це зниження продуктивності впливає будь-який файл, збережений на зовнішній пам’яті. Це означає, що такі програми, як Карти, зберігають великі файли на /sdcard, музичні програми, що зберігають безліч музичних файлів, програми камери та фотографії тощо. Будь-яка операція вводу/виводу, яка виконується із зовнішнім накопичувачем, залежить від накладних витрат FUSE. Але витрати на введення/виведення — не єдина проблема з FUSE.
Проблема №2 - подвійне кешування
Кешування даних є важливим для покращення продуктивності доступу до даних. Зберігаючи важливі фрагменти даних у пам’яті, ядро Linux здатне швидко викликати ці дані за потреби. Але через те, як реалізовано FUSE, Android зберігає вдвічі більше необхідного обсягу кешу.
Як демонструє пан Ковальчик, очікується, що файл розміром 10 МБ буде збережено в кеші рівно 10 МБ, але натомість збільшиться до розміру кешу приблизно на 20 Мб. Це проблематично на пристроях з меншою кількістю оперативної пам’яті, оскільки ядро Linux використовує кеш сторінок для зберігання даних у пам'ять. Пан Ковальчик перевірив проблему подвійного кешування за допомогою такого підходу:
- Створіть файл із відомим розміром (для перевірки, 10 МБ)
- Скопіюйте його в /sdcard
- Видалити кеш сторінки
- Зробіть знімок використання кешу сторінки
- Прочитайте тестовий файл
- Зробіть ще один знімок використання кешу сторінки
Він виявив, що перед його тестом 241 Мбайт використовувався ядром для кешу сторінок. Після того, як він прочитав свій тестовий файл, він очікував побачити 251 Мбайт, використаний для кешу сторінки. Натомість він виявив, що це ядро використовує 263 Мб для кешу сторінок - о вдвічі більше, ніж очікувалося. Це відбувається тому, що дані спочатку кешуються програмою користувача, яка спочатку здійснила виклик введення-виведення (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 Backstage podcast, працював над тим, щоб зробити SDCardFS доступною для всіх пристроїв у майбутній версії ядра. Наразі ви можете бачити їхній прогрес робота в АОСП.
Як Розробник Google пояснював раніше, найбільшою проблемою під час впровадження внутрішньоядерного рішення є те, як зіставити назву пакета з Ідентифікатор програми, необхідний пакету для доступу до власних даних у зовнішньому сховищі, не вимагаючи їх дозволи. Але ця заява була зроблена рік тому, і ми досягли точки, коли команда називає SDCardFS своєю «наступною великою справою». Вони вже підтвердили, що страшна помилка позначки часу було виправлено завдяки відходу від FUSE, тому ми можемо з нетерпінням чекати всіх змін, які виникли після відмови від FUSE.
Омани перевірки фактів
Якщо ви зайшли так далеко в статтю, то вам дяка за те, що ви все встигли! Я хотів прояснити кілька запитань, які виникли у мене під час написання цієї статті:
- SDCardFS має нічого спільного з справжніми SD-картами. Він просто названий так, оскільки він обробляє доступ до введення/виведення для /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 минулого року, деякі розробники спеціального ПЗУ та ядра вирішили застосувати його у своїй роботі. CyanogenMOD в якийсь момент розглядав його впровадження, але відмінив його, коли користувачі зіткнулися з проблемами з їхніми фотографіями. Але ми сподіваємося, що Google перейме владу над цим проектом, і користувачі Android на всіх майбутніх пристроях зможуть скористатися перевагами вдосконалень, запроваджених після відмови від FUSE.