Погружение в SDCardFS: как замена FUSE от Google снизит накладные расходы на ввод-вывод

Углубленное исследование SDCardFS, замены FUSE от Google, и того, как ее реализация снизит накладные расходы ввода-вывода.

Несколько месяцев назад Google добавил что-то под названием «SDCardFS» в официальные ветки AOSP для ядра Linux. Тогда этот шаг заметили только некоторые разработчики ядра, но в остальном оставался незамеченным большинством пользователей. В этом нет ничего удивительного, учитывая тот факт, что большинство пользователей, включая меня, на самом деле не знают, что происходит под капотом ОС Android и ее ядра.

Однако самый последний эпизод Разработчики Android за кулисами подкаст возобновил интерес к этой теме. В подкасте, организованном Четом Хаазе (старшим инженером-программистом 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).

Две совершенно разные «SD-карты»

Google надеялся, что производители последуют их примеру и избавятся от SD-карт. К счастью, со временем производители телефонов смогли закупить эти компоненты большей емкости, оставаясь при этом экономически эффективными, поэтому потребность в SD-картах начала иссякать. Но соглашения об именах сохраняются, чтобы уменьшить количество усилий, которые разработчикам и OEM-производителям придется приложить для внесения изменений. В настоящее время, когда мы говорим о «внешнем хранилище», мы имеем в виду либо одно из двух: фактическая съемная карта 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 отключило виртуальный раздел, прежде чем компьютер сможет получить доступ к данным. Если бы пользователь хотел использовать свое устройство, когда оно подключено к сети, многие вещи будут отображаться как недоступные.

внедрение протокола передачи мультимедиа (MTP) решил эту первую проблему. При подключении к сети ваш компьютер видит ваше устройство как устройство для хранения мультимедиа. Он запрашивает список файлов с вашего телефона, а MTP возвращает список файлов, которые компьютер может загрузить с устройства. Когда файл запрашивается на удаление, MTP отправляет команду на удаление запрошенного файла из хранилища. В отличие от режима USB Mass Storage, который фактически монтирует SD-карту, MTP позволяет пользователю продолжать использовать свое устройство, когда оно подключено. Более того, файловая система телефона Android больше не имеет значения для распознавания компьютером файлов на устройстве.

Во-вторых, VFAT не обеспечивал того надежного управления разрешениями, которое требовалось Google. Вначале многие разработчики приложений рассматривали «sdcard» как свалку данных своих приложений, не имея единого представления о том, где хранить свои файлы. Многие приложения просто создают папку с именем своего приложения и сохраняют там свои файлы.

Почти каждое приложение, существовавшее в то время, требовало ЗАПИСАТЬ_EXTERNAL_STORAGE разрешение на запись файлов своих приложений на внешнее хранилище. Однако более тревожным был тот факт, что почти каждое приложение также требовало ЧТЕНИЕ_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 отличается от модулей ядра, поскольку позволяет непривилегированным пользователям писать виртуальные файловые системы. Причина, по которой Google внедрил FUSE, довольно проста — он сделал то, что они хотели, и уже был хорошо понято и документировано в мире Linux. Чтобы процитировать Разработчик Google по этому поводу:

«Поскольку FUSE — это хороший стабильный API, при переходе между версиями ядра практически не требуется никаких работ по обслуживанию. Если бы мы перешли на решение, встроенное в ядро, мы бы подписались на поддержку набора исправлений для каждой стабильной версии ядра», — Джефф Шарки, инженер-программист в Google.

Однако стало совершенно ясно, что накладные расходы FUSE, помимо других проблем, приводят к снижению производительности. Разработчик, с которым я говорил по этому поводу, Михал Ковальчик, написал отличный пост в блоге более года назад с подробным описанием текущих проблем с FUSE. Более технические подробности можно прочитать в его блоге, но я опишу его выводы (с его разрешения) более простым языком.


Проблема с предохранителем

В Android демон пользовательского пространства «sdcard» использует FUSE для монтирования /dev/fuse в эмулируемый каталог внешнего хранилища при загрузке. После этого демон sdcard опрашивает устройство FUSE на наличие ожидающих сообщений от ядра. Если вы слушали подкаст, возможно, вы слышали, как г-н Лемаршан упомянул о том, что FUSE вводит накладные расходы во время операций ввода-вывода — вот что, по сути, происходит.

В реальном мире это снижение производительности влияет любой файл, хранящийся на внешнем хранилище.

Проблема № 1 — Накладные расходы ввода-вывода

Допустим, мы создаем простой текстовый файл с именем «test.txt» и сохраняем его в /sdcard/test.txt (который, допустим, напоминаю вам, что на самом деле /data/media/0/test.txt предполагает, что текущий пользователь является основным пользователем на устройство). Если бы мы хотели прочитать (команда cat) этот файл, мы ожидали бы, что система выдаст 3 команды: открыть, прочитать и затем закрыть. Действительно, как демонстрирует г-н Ковальчик, используя след, вот что происходит:

Но поскольку файл находится во внешнем хранилище, которым управляет демон sdcard, необходимо выполнить множество дополнительных операций. По словам г-на Ковальчика, необходимо, по сути, 8 дополнительных шагов для каждая из этих 3 отдельных команд:

  1. Приложение пользовательского пространства выдает системный вызов, который будет обрабатываться драйвером FUSE в ядре (мы видим это в первом выводе strace)
  2. Драйвер FUSE в ядре уведомляет демон пользовательского пространства (sdcard) о новом запросе
  3. Демон пользовательского пространства читает /dev/fuse
  4. Демон пользовательского пространства анализирует команду и распознает файловую операцию (например. открыть)
  5. Демон пользовательского пространства отправляет системный вызов фактической файловой системе (EXT4)
  6. Ядро обрабатывает физический доступ к данным и отправляет данные обратно в пользовательское пространство.
  7. Пользовательское пространство изменяет (или нет) данные и снова передает их через /dev/fuse в ядро.
  8. Ядро завершает исходный системный вызов и перемещает данные в реальное приложение пользовательского пространства (в нашем примере 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 используют страничный кеш для хранения данных. Память. Г-н Ковальчик протестировал проблему двойного кэширования, используя следующий подход:

  1. Создайте файл известного размера (для тестирования 10 МБ).
  2. Скопируйте его в /sdcard
  3. Удалить кеш страницы
  4. Сделайте снимок использования страничного кэша
  5. Прочтите тестовый файл
  6. Сделайте еще один снимок использования страничного кэша.

Он обнаружил, что до его теста ядро ​​использовало 241 МБ для кэша страниц. Прочитав тестовый файл, он ожидал увидеть 251 МБ, используемые для кэша страниц. Вместо этого он обнаружил, что это ядро ​​использует 263 МБ для кэша страниц - о в два раза больше, чем ожидалось. Причина, по которой это происходит, заключается в том, что данные сначала кэшируются пользовательским приложением, которое первоначально выдало вызов ввода-вывода (FUSE), а затем демоном SD-карты (EXT4 FS).

Проблема №3 — Неполная реализация FAT32

Есть еще две проблемы, связанные с использованием FUSE, эмулирующего FAT32, которые менее широко известны в сообществе Android.

Первый включает в себя неправильные временные метки. Если вы когда-либо передавали файл (например, фотографию) и замечали, что временная метка неверна, это связано с реализацией FUSE в Android. Этот вопрос имеет существовал для годы. Если быть более конкретным, то проблема связана с утайм() системный вызов, который позволяет изменить время доступа и изменения файла. К сожалению, вызовы демона sdcard от имени обычного пользователя не имеют надлежащего разрешения на выполнение этого системного вызова. Для этого есть обходные пути, но они требуют от вас иметь root-доступ.

Если вы когда-либо передавали файл (например, фотографию) и замечали, что временная метка неверна, это связано с реализацией FUSE в Android.

Следующая проблема больше касается предприятий, использующих что-то вроде смартSD-карта. До FUSE производители приложений могли отслеживать Флаг O_DIRECT для связи со встроенным микроконтроллером в карте. С помощью FUSE разработчики могут получить доступ только к кэшированной версии файла и не могут видеть какие-либо команды, отправленные микроконтроллером. Это проблематично для некоторых корпоративных/государственных/банковских приложений, которые взаимодействуют с дополнительными картами microSD.


Сброс FUSE для SDCardFS

Некоторые OEM-производители сразу заметили эти проблемы и начали искать встроенное в ядро ​​решение для замены FUSE. Например, компания Samsung разработала SDCardFS который основан на WrapFS. Это встроенное в ядро ​​решение эмулирует FAT32 так же, как это делает FUSE, но исключает накладные расходы на ввод-вывод, двойное кэширование и другие проблемы, о которых я упоминал выше. (Да, позвольте мне повторить эту мысль, это решение, которое сейчас реализует Google, основано на работе Samsung).

Сами Google наконец-то признали недостатки, связанные с FUSE, поэтому они начали переходить к встроенному в ядро ​​уровню эмуляции FAT32, разработанному Samsung. Компания, как указано в Разработчики Android за кулисами подкаст работает над тем, чтобы сделать SDCardFS доступной для всех устройств в следующей версии ядра. В настоящее время вы можете видеть прогресс их работа в АОСП.

Как Разработчик Google объяснил ранее, самая большая проблема при реализации встроенного решения заключается в том, как сопоставить имя пакета с идентификатор приложения, необходимый пакету для доступа к собственным данным во внешнем хранилище без каких-либо требований. разрешения. Но это заявление было сделано год назад, и мы подошли к моменту, когда команда называет SDCardFS своей «следующей большой вещью». Они уже подтвердили, что ужасная ошибка временной метки было исправлено благодаря отказу от FUSE, поэтому мы можем с нетерпением ожидать всех изменений, связанных с отказом от FUSE.


Заблуждения, связанные с проверкой фактов

Если вы дошли до этой статьи, то спасибо за то, что до сих пор все успеваете! Я хотел прояснить несколько вопросов, которые у меня возникли при написании этой статьи:

  • SDCardFS имеет ничего общего с реальными SD-картами. Он назван так потому, что обрабатывает доступ ввода-вывода для /sdcard. И, как вы, наверное, помните, /sdcard — это устаревшая метка, относящаяся к «внешнему» хранилищу вашего устройства (где приложения хранят свои медиафайлы).
  • SDCardFS — это не традиционная файловая система например FAT32, EXT4 или F2FS. Это стекируемая файловая система-оболочка, которая передает команды нижним эмулируемым файловым системам (в данном случае это будет FAT32 на /sdcard).
  • В отношении МТП ничего не изменится.. Вы продолжите использовать MTP для передачи файлов на ваш компьютер и с него (пока Google не выберет лучший протокол). Но по крайней мере ошибка временной метки будет исправлена!
  • Как упоминалось ранее, когда Google ссылается на «Внешнее хранилище», они либо имеют в виду (фактически целей) внутренний виртуальный раздел /sdcard FAT32 ИЛИ речь идет о реальной физической съемной карте microSD. карта. Терминология сбивает с толку, но это то, что нас поражает.

Заключение

Отходя от FUSE и внедряя уровень эмуляции FAT32 в ядре (SDCardFS), Google сократит значительные накладные расходы на ввод-вывод, устранение двойного кэширования и решение некоторых неясных проблем, связанных с эмуляцией FUSE ФАТ32.

Поскольку эти изменения будут внесены в ядро, их можно будет развернуть без выпуска основной новой версии Android. Некоторые пользователи ожидают увидеть эти изменения официально реализованными в Android 8, но это возможно. для любого будущего OTA на устройстве Pixel будет использоваться ядро ​​Linux версии 4.1, над которым работает Google. на.

Для некоторых из вас SDCardFS не является новой концепцией. Фактически, устройства Samsung используют его уже много лет (в конце концов, именно они его разработали). С тех пор как SDCardFS была представлена ​​в AOSP в прошлом году, некоторые разработчики пользовательских ПЗУ и ядра решили внедрить ее в свою работу. CyanogenMOD в какой-то момент рассматривал возможность его реализации, но откатил его, когда пользователи столкнулись с проблемами с фотографиями. Но мы надеемся, что, когда Google возьмет на себя управление этим проектом, пользователи Android на всех будущих устройствах смогут воспользоваться преимуществами улучшений, появившихся после отказа от FUSE.